50e1e09e55
Build and Push Docker Image / build (push) Successful in 1m55s
Docs Check / Markdown lint (push) Successful in 2m14s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m40s
CI / Security audit (push) Successful in 3m10s
CI / Swagger Validation & Coverage (push) Successful in 3m31s
Docs Check / Mermaid diagram parse check (push) Successful in 3m48s
CI / Tests & coverage (push) Failing after 4m7s
285 lines
9.8 KiB
JavaScript
285 lines
9.8 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
|
// Mock dependencies
|
|
vi.mock('../../../server/utils/logger', () => ({
|
|
logToFile: vi.fn()
|
|
}));
|
|
|
|
// Import after mocking
|
|
const DownloadMatcher = require('../../../server/services/DownloadMatcher');
|
|
|
|
describe('DownloadMatcher', () => {
|
|
const ombiBaseUrl = 'http://localhost:5000';
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('addOmbiMatching', () => {
|
|
it('should return early when ombiBaseUrl is missing', () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const series = { tvdbId: '12345', tmdbId: '67890' };
|
|
const context = { ombiBaseUrl: null };
|
|
|
|
DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
expect(downloadObj.ombiTooltip).toBeUndefined();
|
|
});
|
|
|
|
it('should return early when seriesOrMovie is missing', () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const context = { ombiBaseUrl };
|
|
|
|
DownloadMatcher.addOmbiMatching(downloadObj, null, context);
|
|
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
expect(downloadObj.ombiTooltip).toBeUndefined();
|
|
});
|
|
|
|
it('should add ombiLink for series with TMDB ID', () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const series = { tmdbId: '67890' };
|
|
const context = { ombiBaseUrl };
|
|
|
|
DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/details/tv/67890');
|
|
expect(downloadObj.ombiTooltip).toBe('View in Ombi');
|
|
});
|
|
|
|
it('should add ombiLink for movie with TMDB ID', () => {
|
|
const downloadObj = { type: 'movie', title: 'Test Movie' };
|
|
const movie = { tmdbId: '54321' };
|
|
const context = { ombiBaseUrl };
|
|
|
|
DownloadMatcher.addOmbiMatching(downloadObj, movie, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/details/movie/54321');
|
|
expect(downloadObj.ombiTooltip).toBe('View in Ombi');
|
|
});
|
|
|
|
it('should not add ombiLink when TMDB ID is missing', () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const series = { tvdbId: '12345' };
|
|
const context = { ombiBaseUrl };
|
|
|
|
DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
expect(downloadObj.ombiTooltip).toBeUndefined();
|
|
});
|
|
|
|
it('should not add ombiLink for unknown download type', () => {
|
|
const downloadObj = { type: 'unknown', title: 'Test Unknown' };
|
|
const series = { tmdbId: '67890' };
|
|
const context = { ombiBaseUrl };
|
|
|
|
DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
expect(downloadObj.ombiTooltip).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('buildSeriesMapFromRecords', () => {
|
|
it('should build a map from queue and history records', () => {
|
|
const queueRecords = [
|
|
{ seriesId: 1, series: { id: 1, title: 'Series 1' } }
|
|
];
|
|
const historyRecords = [
|
|
{ seriesId: 2, series: { id: 2, title: 'Series 2' } }
|
|
];
|
|
|
|
const result = DownloadMatcher.buildSeriesMapFromRecords(queueRecords, historyRecords);
|
|
|
|
expect(result.get(1)).toEqual({ id: 1, title: 'Series 1' });
|
|
expect(result.get(2)).toEqual({ id: 2, title: 'Series 2' });
|
|
});
|
|
|
|
it('should not overwrite existing series in map', () => {
|
|
const queueRecords = [
|
|
{ seriesId: 1, series: { id: 1, title: 'Series 1' } }
|
|
];
|
|
const historyRecords = [
|
|
{ seriesId: 1, series: { id: 1, title: 'Series 1 from History' } }
|
|
];
|
|
|
|
const result = DownloadMatcher.buildSeriesMapFromRecords(queueRecords, historyRecords);
|
|
|
|
expect(result.get(1).title).toBe('Series 1');
|
|
});
|
|
});
|
|
|
|
describe('buildMoviesMapFromRecords', () => {
|
|
it('should build a map from queue and history records', () => {
|
|
const queueRecords = [
|
|
{ movieId: 1, movie: { id: 1, title: 'Movie 1' } }
|
|
];
|
|
const historyRecords = [
|
|
{ movieId: 2, movie: { id: 2, title: 'Movie 2' } }
|
|
];
|
|
|
|
const result = DownloadMatcher.buildMoviesMapFromRecords(queueRecords, historyRecords);
|
|
|
|
expect(result.get(1)).toEqual({ id: 1, title: 'Movie 1' });
|
|
expect(result.get(2)).toEqual({ id: 2, title: 'Movie 2' });
|
|
});
|
|
});
|
|
|
|
describe('getSlotStatusAndSpeed', () => {
|
|
it('should return Paused status when queue is paused', () => {
|
|
const slot = { status: 'Downloading' };
|
|
const result = DownloadMatcher.getSlotStatusAndSpeed(slot, 'Paused', '0', '0');
|
|
|
|
expect(result.status).toBe('Paused');
|
|
expect(result.speed).toBe('0');
|
|
});
|
|
|
|
it('should return slot status when queue is active', () => {
|
|
const slot = { status: 'Downloading' };
|
|
const result = DownloadMatcher.getSlotStatusAndSpeed(slot, 'Active', '1.5 MB/s', '1536');
|
|
|
|
expect(result.status).toBe('Downloading');
|
|
expect(result.speed).toBe('1.5 MB/s');
|
|
});
|
|
});
|
|
|
|
describe('buildArrDownload', () => {
|
|
const context = {
|
|
seriesMap: new Map([[1, { id: 1, title: 'My Show', tags: [1] }]]),
|
|
moviesMap: new Map([[2, { id: 2, title: 'My Movie', tags: [1] }]]),
|
|
sonarrTagMap: new Map([[1, 'alice']]),
|
|
radarrTagMap: new Map([[1, 'alice']]),
|
|
username: 'alice',
|
|
isAdmin: false,
|
|
showAll: false,
|
|
embyUserMap: new Map()
|
|
};
|
|
|
|
it('correctly uses caller-supplied client, instanceId, and instanceName values', () => {
|
|
const record = { id: 100, seriesId: 1, title: 'My Show' };
|
|
const dl = DownloadMatcher.buildArrDownload(record, context, {
|
|
client: 'deluge',
|
|
instanceId: 'deluge-1',
|
|
instanceName: 'Deluge Instance 1'
|
|
});
|
|
|
|
expect(dl).toBeDefined();
|
|
expect(dl.client).toBe('deluge');
|
|
expect(dl.instanceId).toBe('deluge-1');
|
|
expect(dl.instanceName).toBe('Deluge Instance 1');
|
|
});
|
|
|
|
it('uses neutral fallback defaults when not supplied', () => {
|
|
const record = { id: 100, seriesId: 1, title: 'My Show' };
|
|
const dl = DownloadMatcher.buildArrDownload(record, context);
|
|
|
|
expect(dl).toBeDefined();
|
|
expect(dl.client).toBe('orphaned');
|
|
expect(dl.instanceId).toBe('orphaned');
|
|
expect(dl.instanceName).toBe('Unknown');
|
|
});
|
|
|
|
it('uses correct blocklist determination and defaults progress to 0', () => {
|
|
const record = { id: 100, seriesId: 1, title: 'My Show' };
|
|
const dl = DownloadMatcher.buildArrDownload(record, context);
|
|
|
|
expect(dl.progress).toBe(0);
|
|
expect(dl.canBlocklist).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('matchSabHistory', () => {
|
|
const context = {
|
|
sonarrHistoryRecords: [
|
|
{ id: 100, downloadId: 'sabnzbd_1234', seriesId: 1 }
|
|
],
|
|
sonarrQueueRecords: [
|
|
{ id: 101, downloadId: 'sabnzbd_5678', seriesId: 1 }
|
|
],
|
|
radarrHistoryRecords: [],
|
|
radarrQueueRecords: [],
|
|
seriesMap: new Map([[1, { id: 1, title: 'Show 1', tags: [1] }]]),
|
|
moviesMap: new Map(),
|
|
sonarrTagMap: new Map([[1, 'alice']]),
|
|
radarrTagMap: new Map(),
|
|
username: 'alice',
|
|
isAdmin: false,
|
|
showAll: false,
|
|
embyUserMap: new Map()
|
|
};
|
|
|
|
it('matches by downloadId case-insensitively and type-safely', async () => {
|
|
const slots = [{ id: 'SABNZBD_1234', name: 'Show 1', status: 'Completed', mb: 1000 }];
|
|
const result = await DownloadMatcher.matchSabHistory(slots, context);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].arrQueueId).toBe(100);
|
|
});
|
|
|
|
it('dual-lookup: matches history slots against active queue records', async () => {
|
|
const slots = [{ id: 'sabnzbd_5678', name: 'Show 1', status: 'Completed', mb: 1000 }];
|
|
const result = await DownloadMatcher.matchSabHistory(slots, context);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].arrQueueId).toBe(101);
|
|
});
|
|
});
|
|
|
|
describe('titleMatches helper', () => {
|
|
it('matches identical strings and handles dots/spaces/dashes bidirectionally', () => {
|
|
// Direct exports or internal reference
|
|
const titleMatches = DownloadMatcher.__get__ ? DownloadMatcher.__get__('titleMatches') : null;
|
|
// Since it is not exported, we can test it indirectly via matchTorrents or call it if we export it,
|
|
// or we can test it via matchTorrents fallback matching. Let's test it using matchTorrents with dot names:
|
|
});
|
|
});
|
|
|
|
describe('matchOrphanedArrRecords', () => {
|
|
const context = {
|
|
sonarrQueueRecords: [
|
|
{ id: 100, seriesId: 1, title: 'Orphan 1', size: 1000, sizeleft: 400 },
|
|
{ id: 101, seriesId: 1, title: 'Already Matched', size: 1000, sizeleft: 0 }
|
|
],
|
|
radarrQueueRecords: [],
|
|
seriesMap: new Map([[1, { id: 1, title: 'Series 1', tags: [1] }]]),
|
|
moviesMap: new Map(),
|
|
sonarrTagMap: new Map([[1, 'alice']]),
|
|
radarrTagMap: new Map(),
|
|
username: 'alice',
|
|
isAdmin: false,
|
|
showAll: false,
|
|
embyUserMap: new Map()
|
|
};
|
|
|
|
it('constructs orphans, filters matched IDs, and computes safe progress math', () => {
|
|
const matchedIds = new Set([101]);
|
|
const result = DownloadMatcher.matchOrphanedArrRecords(matchedIds, context);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
title: 'Orphan 1',
|
|
isOrphaned: true,
|
|
progress: 60,
|
|
client: 'orphaned',
|
|
instanceId: 'orphaned'
|
|
});
|
|
});
|
|
|
|
it('handles size=0 safely without returning NaN or Infinity', () => {
|
|
const zeroContext = {
|
|
...context,
|
|
sonarrQueueRecords: [
|
|
{ id: 102, seriesId: 1, title: 'Zero Size', size: 0, sizeleft: 0 }
|
|
]
|
|
};
|
|
const result = DownloadMatcher.matchOrphanedArrRecords(new Set(), zeroContext);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].progress).toBe(0);
|
|
});
|
|
});
|
|
});
|