9621aec453
- Add tests for Ombi configuration parsing (OMBI_INSTANCES JSON array, legacy fallback) - Add tests for OmbiClient API methods (movie/TV requests, search by TMDB/IMDB/TVDB) - Add tests for OmbiRetriever caching, queue, and search functionality - Add tests for arrRetrieverRegistry initialization and retrieval methods - Add tests for DownloadMatcher.addOmbiMatching integration - Add tests for DownloadAssembler Ombi link generation utilities - Export addOmbiMatching from DownloadMatcher module
369 lines
12 KiB
JavaScript
369 lines
12 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import nock from 'nock';
|
|
|
|
// Mock dependencies
|
|
vi.mock('../../../server/utils/logger', () => ({
|
|
logToFile: vi.fn()
|
|
}));
|
|
|
|
// Import after mocking
|
|
const DownloadMatcher = require('../../../server/services/DownloadMatcher');
|
|
const OmbiRetriever = require('../../../server/clients/OmbiRetriever');
|
|
|
|
describe('DownloadMatcher', () => {
|
|
const ombiBaseUrl = 'http://localhost:5000';
|
|
|
|
beforeEach(() => {
|
|
nock.cleanAll();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
nock.cleanAll();
|
|
});
|
|
|
|
describe('addOmbiMatching', () => {
|
|
it('should return early when ombiRetriever is missing', async () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const series = { tvdbId: '12345', tmdbId: '67890' };
|
|
const context = { ombiRetriever: null, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
expect(downloadObj.ombiRequestId).toBeUndefined();
|
|
});
|
|
|
|
it('should return early when ombiBaseUrl is missing', async () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const series = { tvdbId: '12345', tmdbId: '67890' };
|
|
|
|
const mockRetriever = {
|
|
findTvRequest: vi.fn(),
|
|
searchTv: vi.fn()
|
|
};
|
|
|
|
const context = { ombiRetriever: mockRetriever, ombiBaseUrl: null };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(mockRetriever.findTvRequest).not.toHaveBeenCalled();
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
});
|
|
|
|
it('should return early when seriesOrMovie is missing', async () => {
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
|
|
const mockRetriever = {
|
|
findTvRequest: vi.fn(),
|
|
searchTv: vi.fn()
|
|
};
|
|
|
|
const context = { ombiRetriever: mockRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, null, context);
|
|
|
|
expect(mockRetriever.findTvRequest).not.toHaveBeenCalled();
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
});
|
|
|
|
it('should add ombiLink and ombiRequestId for TV request found by TVDB ID', async () => {
|
|
const mockMovieRequests = [];
|
|
const mockTvRequests = [
|
|
{ id: 101, title: 'Test Show', type: 'tv', theTvDbId: '12345' }
|
|
];
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/movie')
|
|
.reply(200, mockMovieRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/tv')
|
|
.reply(200, mockTvRequests);
|
|
|
|
const ombiRetriever = new OmbiRetriever({
|
|
id: 'test-ombi',
|
|
name: 'Test Ombi',
|
|
url: ombiBaseUrl,
|
|
apiKey: 'test-key'
|
|
});
|
|
|
|
const downloadObj = { type: 'series', title: 'Test Show' };
|
|
const series = { tvdbId: '12345', tmdbId: '67890' };
|
|
const context = { ombiRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/#/request/tv/101');
|
|
expect(downloadObj.ombiRequestId).toBe(101);
|
|
expect(downloadObj.ombiTooltip).toBe('Request');
|
|
});
|
|
|
|
it('should add ombiLink and ombiRequestId for TV request found by TMDB ID fallback', async () => {
|
|
const mockMovieRequests = [];
|
|
const mockTvRequests = [
|
|
{ id: 102, title: 'Test Show TMDB', type: 'tv', theMovieDbId: '67890' }
|
|
];
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/movie')
|
|
.reply(200, mockMovieRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/tv')
|
|
.reply(200, mockTvRequests);
|
|
|
|
const ombiRetriever = new OmbiRetriever({
|
|
id: 'test-ombi',
|
|
name: 'Test Ombi',
|
|
url: ombiBaseUrl,
|
|
apiKey: 'test-key'
|
|
});
|
|
|
|
const downloadObj = { type: 'series', title: 'Test Show TMDB' };
|
|
const series = { tvdbId: '99999', tmdbId: '67890' };
|
|
const context = { ombiRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/#/request/tv/102');
|
|
expect(downloadObj.ombiRequestId).toBe(102);
|
|
expect(downloadObj.ombiTooltip).toBe('Request');
|
|
});
|
|
|
|
it('should add ombiLink and ombiRequestId for movie request found by TMDB ID', async () => {
|
|
const mockMovieRequests = [
|
|
{ id: 201, title: 'Test Movie', type: 'movie', theMovieDbId: '54321' }
|
|
];
|
|
const mockTvRequests = [];
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/movie')
|
|
.reply(200, mockMovieRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/tv')
|
|
.reply(200, mockTvRequests);
|
|
|
|
const ombiRetriever = new OmbiRetriever({
|
|
id: 'test-ombi',
|
|
name: 'Test Ombi',
|
|
url: ombiBaseUrl,
|
|
apiKey: 'test-key'
|
|
});
|
|
|
|
const downloadObj = { type: 'movie', title: 'Test Movie' };
|
|
const movie = { tmdbId: '54321', imdbId: 'tt54321' };
|
|
const context = { ombiRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, movie, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/#/request/movie/201');
|
|
expect(downloadObj.ombiRequestId).toBe(201);
|
|
expect(downloadObj.ombiTooltip).toBe('Request');
|
|
});
|
|
|
|
it('should add ombiLink and ombiRequestId for movie request found by IMDB ID fallback', async () => {
|
|
const mockMovieRequests = [
|
|
{ id: 202, title: 'Test Movie IMDB', type: 'movie', imdbId: 'tt98765' }
|
|
];
|
|
const mockTvRequests = [];
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/movie')
|
|
.reply(200, mockMovieRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/tv')
|
|
.reply(200, mockTvRequests);
|
|
|
|
const ombiRetriever = new OmbiRetriever({
|
|
id: 'test-ombi',
|
|
name: 'Test Ombi',
|
|
url: ombiBaseUrl,
|
|
apiKey: 'test-key'
|
|
});
|
|
|
|
const downloadObj = { type: 'movie', title: 'Test Movie IMDB' };
|
|
const movie = { tmdbId: '99999', imdbId: 'tt98765' };
|
|
const context = { ombiRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, movie, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/#/request/movie/202');
|
|
expect(downloadObj.ombiRequestId).toBe(202);
|
|
expect(downloadObj.ombiTooltip).toBe('Request');
|
|
});
|
|
|
|
it('should add search link and tooltip when no request found but search succeeds', async () => {
|
|
const mockMovieRequests = [];
|
|
const mockTvRequests = [];
|
|
const mockSearchResult = { id: 12345, title: 'Test Show Search', theTvDbId: '11111' };
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/movie')
|
|
.reply(200, mockMovieRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/tv')
|
|
.reply(200, mockTvRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Search/tv/11111')
|
|
.reply(200, mockSearchResult);
|
|
|
|
const ombiRetriever = new OmbiRetriever({
|
|
id: 'test-ombi',
|
|
name: 'Test Ombi',
|
|
url: ombiBaseUrl,
|
|
apiKey: 'test-key'
|
|
});
|
|
|
|
const downloadObj = { type: 'series', title: 'Test Show Search' };
|
|
const series = { tvdbId: '11111', tmdbId: '22222' };
|
|
const context = { ombiRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, series, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/#/tv/search/12345');
|
|
expect(downloadObj.ombiTooltip).toBe('Search');
|
|
expect(downloadObj.ombiRequestId).toBeUndefined();
|
|
});
|
|
|
|
it('should add movie search link for movie type', async () => {
|
|
const mockMovieRequests = [];
|
|
const mockTvRequests = [];
|
|
const mockSearchResult = { id: 54321, title: 'Test Movie Search', theMovieDbId: '33333' };
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/movie')
|
|
.reply(200, mockMovieRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Request/tv')
|
|
.reply(200, mockTvRequests);
|
|
|
|
nock(ombiBaseUrl)
|
|
.get('/api/v1/Search/movie/33333')
|
|
.reply(200, mockSearchResult);
|
|
|
|
const ombiRetriever = new OmbiRetriever({
|
|
id: 'test-ombi',
|
|
name: 'Test Ombi',
|
|
url: ombiBaseUrl,
|
|
apiKey: 'test-key'
|
|
});
|
|
|
|
const downloadObj = { type: 'movie', title: 'Test Movie Search' };
|
|
const movie = { tmdbId: '33333', imdbId: 'tt33333' };
|
|
const context = { ombiRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, movie, context);
|
|
|
|
expect(downloadObj.ombiLink).toBe('http://localhost:5000/#/movie/search/54321');
|
|
expect(downloadObj.ombiTooltip).toBe('Search');
|
|
expect(downloadObj.ombiRequestId).toBeUndefined();
|
|
});
|
|
|
|
it('should handle errors gracefully without breaking download object', async () => {
|
|
const mockRetriever = {
|
|
findTvRequest: vi.fn().mockRejectedValue(new Error('Ombi API error')),
|
|
searchTv: vi.fn().mockRejectedValue(new Error('Search error'))
|
|
};
|
|
|
|
const downloadObj = { type: 'series', title: 'Test Show Error' };
|
|
const series = { tvdbId: '66666', tmdbId: '77777' };
|
|
const context = { ombiRetriever: mockRetriever, ombiBaseUrl };
|
|
|
|
// Should not throw error
|
|
await expect(DownloadMatcher.addOmbiMatching(downloadObj, series, context)).resolves.not.toThrow();
|
|
|
|
// Download object should still have original data
|
|
expect(downloadObj.title).toBe('Test Show Error');
|
|
expect(downloadObj.ombiLink).toBeUndefined();
|
|
expect(downloadObj.ombiRequestId).toBeUndefined();
|
|
});
|
|
|
|
it('should do nothing for unknown download type', async () => {
|
|
const mockRetriever = {
|
|
findTvRequest: vi.fn(),
|
|
findMovieRequest: vi.fn()
|
|
};
|
|
|
|
const downloadObj = { type: 'unknown', title: 'Unknown Type' };
|
|
const media = { id: 123 };
|
|
const context = { ombiRetriever: mockRetriever, ombiBaseUrl };
|
|
|
|
await DownloadMatcher.addOmbiMatching(downloadObj, media, context);
|
|
|
|
expect(mockRetriever.findTvRequest).not.toHaveBeenCalled();
|
|
expect(mockRetriever.findMovieRequest).not.toHaveBeenCalled();
|
|
expect(downloadObj.ombiLink).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');
|
|
});
|
|
});
|
|
});
|