test: add comprehensive test suite for Ombi integration
- 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
This commit is contained in:
@@ -629,6 +629,7 @@ module.exports = {
|
|||||||
buildSeriesMapFromRecords,
|
buildSeriesMapFromRecords,
|
||||||
buildMoviesMapFromRecords,
|
buildMoviesMapFromRecords,
|
||||||
getSlotStatusAndSpeed,
|
getSlotStatusAndSpeed,
|
||||||
|
addOmbiMatching,
|
||||||
matchSabSlots,
|
matchSabSlots,
|
||||||
matchSabHistory,
|
matchSabHistory,
|
||||||
matchTorrents
|
matchTorrents
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
// Mock the logger and config before importing the registry
|
||||||
|
vi.mock('../../server/utils/logger', () => ({
|
||||||
|
logToFile: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock config to return test data
|
||||||
|
const mockOmbiInstances = [
|
||||||
|
{ id: 'ombi-test', name: 'Test Ombi', url: 'http://localhost:5000', apiKey: 'test-key' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockSonarrInstances = [
|
||||||
|
{ id: 'sonarr-test', name: 'Test Sonarr', url: 'http://localhost:8989', apiKey: 'sonarr-key' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockRadarrInstances = [
|
||||||
|
{ id: 'radarr-test', name: 'Test Radarr', url: 'http://localhost:7878', apiKey: 'radarr-key' }
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mock('../../server/utils/config', () => ({
|
||||||
|
getSonarrInstances: vi.fn(() => mockSonarrInstances),
|
||||||
|
getRadarrInstances: vi.fn(() => mockRadarrInstances),
|
||||||
|
getOmbiInstances: vi.fn(() => mockOmbiInstances)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import the registry after mocking
|
||||||
|
const arrRetrieverRegistry = require('../../server/utils/arrRetrievers');
|
||||||
|
const OmbiRetriever = require('../../server/clients/OmbiRetriever');
|
||||||
|
const ArrRetriever = require('../../server/clients/ArrRetriever');
|
||||||
|
|
||||||
|
describe('arrRetrieverRegistry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset the registry state before each test
|
||||||
|
arrRetrieverRegistry.retrievers.clear();
|
||||||
|
arrRetrieverRegistry.initialized = false;
|
||||||
|
nock.cleanAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initialize', () => {
|
||||||
|
it('should initialize without errors', async () => {
|
||||||
|
await expect(arrRetrieverRegistry.initialize()).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not reinitialize if already initialized', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
const firstRetrieverCount = arrRetrieverRegistry.getOmbiRetrievers().length;
|
||||||
|
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
const secondRetrieverCount = arrRetrieverRegistry.getOmbiRetrievers().length;
|
||||||
|
|
||||||
|
expect(secondRetrieverCount).toBe(firstRetrieverCount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getOmbiRetrievers', () => {
|
||||||
|
it('should return Ombi retrievers only', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const ombiRetrievers = arrRetrieverRegistry.getOmbiRetrievers();
|
||||||
|
expect(ombiRetrievers.length).toBeGreaterThanOrEqual(0);
|
||||||
|
ombiRetrievers.forEach(retriever => {
|
||||||
|
expect(retriever.getRetrieverType()).toBe('ombi');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getOmbiRequests', () => {
|
||||||
|
it('should return movie and TV request arrays', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const result = await arrRetrieverRegistry.getOmbiRequests();
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('movie');
|
||||||
|
expect(result).toHaveProperty('tv');
|
||||||
|
expect(Array.isArray(result.movie)).toBe(true);
|
||||||
|
expect(Array.isArray(result.tv)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors gracefully', async () => {
|
||||||
|
nock('http://localhost:5000')
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(500, { error: 'Server Error' });
|
||||||
|
|
||||||
|
nock('http://localhost:5000')
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(500, { error: 'Server Error' });
|
||||||
|
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const result = await arrRetrieverRegistry.getOmbiRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual({ movie: [], tv: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getOmbiRequestsByType', () => {
|
||||||
|
it('should return grouped requests by type', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const result = await arrRetrieverRegistry.getOmbiRequestsByType();
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('movie');
|
||||||
|
expect(result).toHaveProperty('tv');
|
||||||
|
expect(Array.isArray(result.movie)).toBe(true);
|
||||||
|
expect(Array.isArray(result.tv)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOmbiRequest', () => {
|
||||||
|
it('should return null for unknown type', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
const result = await arrRetrieverRegistry.findOmbiRequest('unknown', { tmdbId: '12345' });
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAllRetrievers', () => {
|
||||||
|
it('should return all retrievers', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const allRetrievers = arrRetrieverRegistry.getAllRetrievers();
|
||||||
|
expect(Array.isArray(allRetrievers)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRetriever', () => {
|
||||||
|
it('should return null for non-existent instance', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const retriever = arrRetrieverRegistry.getRetriever('non-existent');
|
||||||
|
expect(retriever).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRetrieversByType', () => {
|
||||||
|
it('should filter retrievers by type', async () => {
|
||||||
|
await arrRetrieverRegistry.initialize();
|
||||||
|
|
||||||
|
const sonarrRetrievers = arrRetrieverRegistry.getRetrieversByType('sonarr');
|
||||||
|
const radarrRetrievers = arrRetrieverRegistry.getRetrieversByType('radarr');
|
||||||
|
const ombiRetrievers = arrRetrieverRegistry.getRetrieversByType('ombi');
|
||||||
|
|
||||||
|
sonarrRetrievers.forEach(r => expect(r.getRetrieverType()).toBe('sonarr'));
|
||||||
|
radarrRetrievers.forEach(r => expect(r.getRetrieverType()).toBe('radarr'));
|
||||||
|
ombiRetrievers.forEach(r => expect(r.getRetrieverType()).toBe('ombi'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('matching helper functions', () => {
|
||||||
|
it('should expose matchDownload function', () => {
|
||||||
|
expect(arrRetrieverRegistry.matchDownload).toBeDefined();
|
||||||
|
expect(typeof arrRetrieverRegistry.matchDownload).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should expose matchDownloadToArr alias', () => {
|
||||||
|
expect(arrRetrieverRegistry.matchDownloadToArr).toBeDefined();
|
||||||
|
expect(typeof arrRetrieverRegistry.matchDownloadToArr).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should expose aggregateMatch alias', () => {
|
||||||
|
expect(arrRetrieverRegistry.aggregateMatch).toBeDefined();
|
||||||
|
expect(typeof arrRetrieverRegistry.aggregateMatch).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should expose matchingHelper alias', () => {
|
||||||
|
expect(arrRetrieverRegistry.matchingHelper).toBeDefined();
|
||||||
|
expect(typeof arrRetrieverRegistry.matchingHelper).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should expose compareDownloadAndArr alias', () => {
|
||||||
|
expect(arrRetrieverRegistry.compareDownloadAndArr).toBeDefined();
|
||||||
|
expect(typeof arrRetrieverRegistry.compareDownloadAndArr).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
// Mock the logger before importing the client
|
||||||
|
vi.mock('../../../server/utils/logger', () => ({
|
||||||
|
logToFile: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import OmbiClient after mocking
|
||||||
|
const OmbiClient = require('../../../server/clients/OmbiClient');
|
||||||
|
|
||||||
|
describe('OmbiClient', () => {
|
||||||
|
const baseUrl = 'http://localhost:5000';
|
||||||
|
const apiKey = 'test-api-key-12345';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clean up nock after each test
|
||||||
|
nock.cleanAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should initialize with URL and API key', () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
|
||||||
|
expect(client.url).toBe(baseUrl);
|
||||||
|
expect(client.apiKey).toBe(apiKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove trailing slash from URL', () => {
|
||||||
|
const client = new OmbiClient('http://localhost:5000/', apiKey);
|
||||||
|
|
||||||
|
expect(client.url).toBe('http://localhost:5000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up axios with API key header', () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
|
||||||
|
expect(client.axios.defaults.headers['ApiKey']).toBe(apiKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set up axios with 10 second timeout', () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
|
||||||
|
expect(client.axios.defaults.timeout).toBe(10000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getMovieRequests', () => {
|
||||||
|
it('should return movie requests on successful API call', async () => {
|
||||||
|
const mockMovies = [
|
||||||
|
{ id: 1, title: 'Test Movie 1', theMovieDbId: '12345' },
|
||||||
|
{ id: 2, title: 'Test Movie 2', theMovieDbId: '67890' }
|
||||||
|
];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.getMovieRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual(mockMovies);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array on API error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(500, { error: 'Internal Server Error' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.getMovieRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array when response data is null', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, null);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.getMovieRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTvRequests', () => {
|
||||||
|
it('should return TV requests on successful API call', async () => {
|
||||||
|
const mockTvShows = [
|
||||||
|
{ id: 1, title: 'Test Show 1', theTvDbId: '12345' },
|
||||||
|
{ id: 2, title: 'Test Show 2', theTvDbId: '67890' }
|
||||||
|
];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.getTvRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual(mockTvShows);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array on API error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(500, { error: 'Internal Server Error' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.getTvRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array when response data is null', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, null);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.getTvRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchMovieByTmdbId', () => {
|
||||||
|
it('should return movie data for valid TMDB ID', async () => {
|
||||||
|
const mockMovie = {
|
||||||
|
id: 12345,
|
||||||
|
title: 'Test Movie',
|
||||||
|
theMovieDbId: '12345'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/12345')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, mockMovie);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByTmdbId('12345');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockMovie);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for null TMDB ID', async () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByTmdbId(null);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for undefined TMDB ID', async () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByTmdbId(undefined);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null on API error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/12345')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByTmdbId('12345');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchMovieByImdbId', () => {
|
||||||
|
it('should return movie data for valid IMDB ID', async () => {
|
||||||
|
const mockMovie = {
|
||||||
|
id: 12345,
|
||||||
|
title: 'Test Movie',
|
||||||
|
imdbId: 'tt1234567'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/imdb/tt1234567')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, mockMovie);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByImdbId('tt1234567');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockMovie);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for null IMDB ID', async () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByImdbId(null);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null on API error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/imdb/tt1234567')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchMovieByImdbId('tt1234567');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchTvByTvdbId', () => {
|
||||||
|
it('should return TV show data for valid TVDB ID', async () => {
|
||||||
|
const mockShow = {
|
||||||
|
id: 12345,
|
||||||
|
title: 'Test Show',
|
||||||
|
theTvDbId: '12345'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/12345')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, mockShow);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchTvByTvdbId('12345');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockShow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for null TVDB ID', async () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchTvByTvdbId(null);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null on API error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/12345')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchTvByTvdbId('12345');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchTvByTmdbId', () => {
|
||||||
|
it('should return TV show data for valid TMDB ID', async () => {
|
||||||
|
const mockShow = {
|
||||||
|
id: 12345,
|
||||||
|
title: 'Test Show',
|
||||||
|
theMovieDbId: '67890'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/tmdb/67890')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, mockShow);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchTvByTmdbId('67890');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockShow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for null TMDB ID', async () => {
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchTvByTmdbId(null);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null on API error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/tmdb/67890')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.searchTvByTmdbId('67890');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('testConnection', () => {
|
||||||
|
it('should return true for successful connection', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(200, []);
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.testConnection();
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for failed connection', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.reply(401, { error: 'Unauthorized' });
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.testConnection();
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false on network error', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.matchHeader('ApiKey', apiKey)
|
||||||
|
.replyWithError('Network error');
|
||||||
|
|
||||||
|
const client = new OmbiClient(baseUrl, apiKey);
|
||||||
|
const result = await client.testConnection();
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,678 @@
|
|||||||
|
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
// Mock the logger before importing the retriever
|
||||||
|
vi.mock('../../../server/utils/logger', () => ({
|
||||||
|
logToFile: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import OmbiRetriever after mocking
|
||||||
|
const OmbiRetriever = require('../../../server/clients/OmbiRetriever');
|
||||||
|
const ArrRetriever = require('../../../server/clients/ArrRetriever');
|
||||||
|
|
||||||
|
describe('OmbiRetriever', () => {
|
||||||
|
const baseUrl = 'http://localhost:5000';
|
||||||
|
const apiKey = 'test-api-key-12345';
|
||||||
|
const instanceConfig = {
|
||||||
|
id: 'test-ombi-1',
|
||||||
|
name: 'Test Ombi Instance',
|
||||||
|
url: baseUrl,
|
||||||
|
apiKey: apiKey
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should extend ArrRetriever base class', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever).toBeInstanceOf(ArrRetriever);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize with correct properties', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever.id).toBe('test-ombi-1');
|
||||||
|
expect(retriever.name).toBe('Test Ombi Instance');
|
||||||
|
expect(retriever.url).toBe(baseUrl);
|
||||||
|
expect(retriever.apiKey).toBe(apiKey);
|
||||||
|
expect(retriever.baseUrl).toBe(baseUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize cache with empty arrays and maps', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever.cache.movieRequests).toEqual([]);
|
||||||
|
expect(retriever.cache.tvRequests).toEqual([]);
|
||||||
|
expect(retriever.cache.movieMap).toBeInstanceOf(Map);
|
||||||
|
expect(retriever.cache.tvMap).toBeInstanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set cache TTL to 5 minutes', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever.cache.ttl).toBe(5 * 60 * 1000); // 5 minutes in ms
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRetrieverType', () => {
|
||||||
|
it('should return "ombi"', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever.getRetrieverType()).toBe('ombi');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getInstanceId', () => {
|
||||||
|
it('should return configured instance ID', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever.getInstanceId()).toBe('test-ombi-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTags', () => {
|
||||||
|
it('should return empty array', async () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.getTags();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getQueue', () => {
|
||||||
|
it('should return combined movie and TV requests', async () => {
|
||||||
|
const mockMovies = [
|
||||||
|
{ id: 1, title: 'Movie 1', theMovieDbId: '12345' },
|
||||||
|
{ id: 2, title: 'Movie 2', theMovieDbId: '67890' }
|
||||||
|
];
|
||||||
|
const mockTvShows = [
|
||||||
|
{ id: 3, title: 'Show 1', theTvDbId: '11111' }
|
||||||
|
];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.getQueue();
|
||||||
|
|
||||||
|
expect(result.records).toHaveLength(3);
|
||||||
|
expect(result.records[0].title).toBe('Movie 1');
|
||||||
|
expect(result.records[2].title).toBe('Show 1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getHistory', () => {
|
||||||
|
it('should return empty records array', async () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.getHistory();
|
||||||
|
|
||||||
|
expect(result).toEqual({ records: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty records even with options', async () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.getHistory({ pageSize: 10, sortKey: 'date' });
|
||||||
|
|
||||||
|
expect(result).toEqual({ records: [] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('testConnection', () => {
|
||||||
|
it('should return true for successful connection', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, []);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.testConnection();
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for failed connection', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(401, { error: 'Unauthorized' });
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.testConnection();
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isCacheExpired', () => {
|
||||||
|
it('should return true when cache is fresh (never fetched)', () => {
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
expect(retriever.isCacheExpired()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when cache is within TTL', async () => {
|
||||||
|
const mockMovies = [{ id: 1, title: 'Movie 1' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
expect(retriever.isCacheExpired()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when cache is beyond TTL', async () => {
|
||||||
|
const mockMovies = [{ id: 1, title: 'Movie 1' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
// Advance time by 6 minutes (beyond 5-minute TTL)
|
||||||
|
vi.advanceTimersByTime(6 * 60 * 1000);
|
||||||
|
|
||||||
|
expect(retriever.isCacheExpired()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('refreshCache', () => {
|
||||||
|
it('should not refresh if cache is not expired', async () => {
|
||||||
|
const mockMovies = [{ id: 1, title: 'Movie 1', theMovieDbId: '12345' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
// First refresh
|
||||||
|
await retriever.refreshCache();
|
||||||
|
expect(retriever.cache.movieRequests).toHaveLength(1);
|
||||||
|
|
||||||
|
// Reset nock to verify no new calls are made
|
||||||
|
nock.cleanAll();
|
||||||
|
|
||||||
|
// Second refresh should not make API calls
|
||||||
|
await retriever.refreshCache();
|
||||||
|
expect(retriever.cache.movieRequests).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should refresh when cache is expired', async () => {
|
||||||
|
const mockMovies1 = [{ id: 1, title: 'Movie 1', theMovieDbId: '12345' }];
|
||||||
|
const mockMovies2 = [{ id: 1, title: 'Movie 1' }, { id: 2, title: 'Movie 2', theMovieDbId: '67890' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies1);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
// First refresh
|
||||||
|
await retriever.refreshCache();
|
||||||
|
expect(retriever.cache.movieRequests).toHaveLength(1);
|
||||||
|
|
||||||
|
// Advance time beyond TTL
|
||||||
|
vi.advanceTimersByTime(6 * 60 * 1000);
|
||||||
|
|
||||||
|
// Set up new mocks for second refresh
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies2);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
// Second refresh should make API calls
|
||||||
|
await retriever.refreshCache();
|
||||||
|
expect(retriever.cache.movieRequests).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build movie map with TMDB and IMDB IDs', async () => {
|
||||||
|
const mockMovies = [
|
||||||
|
{ id: 1, title: 'Movie 1', theMovieDbId: '12345', imdbId: 'tt12345' },
|
||||||
|
{ id: 2, title: 'Movie 2', theMovieDbId: '67890' }
|
||||||
|
];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
expect(retriever.cache.movieMap.get('12345')).toEqual(mockMovies[0]);
|
||||||
|
expect(retriever.cache.movieMap.get('tt12345')).toEqual(mockMovies[0]);
|
||||||
|
expect(retriever.cache.movieMap.get('67890')).toEqual(mockMovies[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build TV map with TVDB and TMDB IDs', async () => {
|
||||||
|
const mockMovies = [];
|
||||||
|
const mockTvShows = [
|
||||||
|
{ id: 1, title: 'Show 1', theTvDbId: '11111', theMovieDbId: '22222' },
|
||||||
|
{ id: 2, title: 'Show 2', theTvDbId: '33333' }
|
||||||
|
];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
expect(retriever.cache.tvMap.get('11111')).toEqual(mockTvShows[0]);
|
||||||
|
expect(retriever.cache.tvMap.get('22222')).toEqual(mockTvShows[0]);
|
||||||
|
expect(retriever.cache.tvMap.get('33333')).toEqual(mockTvShows[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle API errors gracefully', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(500, { error: 'Server Error' });
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(500, { error: 'Server Error' });
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
|
||||||
|
// Should not throw error
|
||||||
|
await expect(retriever.refreshCache()).resolves.not.toThrow();
|
||||||
|
|
||||||
|
// Cache should remain empty but not crash
|
||||||
|
expect(retriever.cache.movieRequests).toEqual([]);
|
||||||
|
expect(retriever.cache.tvRequests).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getMovieRequests', () => {
|
||||||
|
it('should return cached movie requests on cache hit', async () => {
|
||||||
|
const mockMovies = [{ id: 1, title: 'Movie 1', theMovieDbId: '12345' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
// Reset nock to ensure no new API calls
|
||||||
|
nock.cleanAll();
|
||||||
|
|
||||||
|
const result = await retriever.getMovieRequests();
|
||||||
|
expect(result).toEqual(mockMovies);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch and return movie requests on cache miss', async () => {
|
||||||
|
const mockMovies = [{ id: 1, title: 'Movie 1', theMovieDbId: '12345' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.getMovieRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual(mockMovies);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTvRequests', () => {
|
||||||
|
it('should return cached TV requests on cache hit', async () => {
|
||||||
|
const mockMovies = [];
|
||||||
|
const mockTvShows = [{ id: 1, title: 'Show 1', theTvDbId: '11111' }];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
// Reset nock to ensure no new API calls
|
||||||
|
nock.cleanAll();
|
||||||
|
|
||||||
|
const result = await retriever.getTvRequests();
|
||||||
|
expect(result).toEqual(mockTvShows);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch and return TV requests on cache miss', async () => {
|
||||||
|
const mockMovies = [];
|
||||||
|
const mockTvShows = [{ id: 1, title: 'Show 1', theTvDbId: '11111' }];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.getTvRequests();
|
||||||
|
|
||||||
|
expect(result).toEqual(mockTvShows);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findMovieRequest', () => {
|
||||||
|
it('should find movie by TMDB ID from cache', async () => {
|
||||||
|
const mockMovies = [
|
||||||
|
{ id: 1, title: 'Movie 1', theMovieDbId: '12345', imdbId: 'tt12345' }
|
||||||
|
];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.findMovieRequest('12345');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockMovies[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find movie by IMDB ID when TMDB ID not found', async () => {
|
||||||
|
const mockMovies = [
|
||||||
|
{ id: 1, title: 'Movie 1', theMovieDbId: '12345', imdbId: 'tt12345' }
|
||||||
|
];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.findMovieRequest('99999', 'tt12345');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockMovies[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when movie not found', async () => {
|
||||||
|
const mockMovies = [{ id: 1, title: 'Movie 1', theMovieDbId: '12345' }];
|
||||||
|
const mockTvShows = [];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.findMovieRequest('99999');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findTvRequest', () => {
|
||||||
|
it('should find TV show by TVDB ID from cache', async () => {
|
||||||
|
const mockMovies = [];
|
||||||
|
const mockTvShows = [
|
||||||
|
{ id: 1, title: 'Show 1', theTvDbId: '11111', theMovieDbId: '22222' }
|
||||||
|
];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.findTvRequest('11111');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockTvShows[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find TV show by TMDB ID when TVDB ID not found', async () => {
|
||||||
|
const mockMovies = [];
|
||||||
|
const mockTvShows = [
|
||||||
|
{ id: 1, title: 'Show 1', theTvDbId: '11111', theMovieDbId: '22222' }
|
||||||
|
];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.findTvRequest('99999', '22222');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockTvShows[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when TV show not found', async () => {
|
||||||
|
const mockMovies = [];
|
||||||
|
const mockTvShows = [{ id: 1, title: 'Show 1', theTvDbId: '11111' }];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.findTvRequest('99999');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchMovie', () => {
|
||||||
|
it('should search by TMDB ID first', async () => {
|
||||||
|
const mockSearchResult = {
|
||||||
|
id: 12345,
|
||||||
|
title: 'Searched Movie',
|
||||||
|
theMovieDbId: '12345'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/12345')
|
||||||
|
.reply(200, mockSearchResult);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.searchMovie('12345');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockSearchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to IMDB ID when TMDB search fails', async () => {
|
||||||
|
const mockSearchResult = {
|
||||||
|
id: 12345,
|
||||||
|
title: 'Searched Movie',
|
||||||
|
imdbId: 'tt12345'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/12345')
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/imdb/tt12345')
|
||||||
|
.reply(200, mockSearchResult);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.searchMovie('12345', 'tt12345');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockSearchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when both searches fail', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/12345')
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/movie/imdb/tt12345')
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.searchMovie('12345', 'tt12345');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchTv', () => {
|
||||||
|
it('should search by TVDB ID first', async () => {
|
||||||
|
const mockSearchResult = {
|
||||||
|
id: 11111,
|
||||||
|
title: 'Searched Show',
|
||||||
|
theTvDbId: '11111'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/11111')
|
||||||
|
.reply(200, mockSearchResult);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.searchTv('11111');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockSearchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to TMDB ID when TVDB search fails', async () => {
|
||||||
|
const mockSearchResult = {
|
||||||
|
id: 11111,
|
||||||
|
title: 'Searched Show',
|
||||||
|
theMovieDbId: '22222'
|
||||||
|
};
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/11111')
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/tmdb/22222')
|
||||||
|
.reply(200, mockSearchResult);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.searchTv('11111', '22222');
|
||||||
|
|
||||||
|
expect(result).toEqual(mockSearchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when both searches fail', async () => {
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/11111')
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Search/tv/tmdb/22222')
|
||||||
|
.reply(404, { error: 'Not Found' });
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
const result = await retriever.searchTv('11111', '22222');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCacheStats', () => {
|
||||||
|
it('should return cache statistics', async () => {
|
||||||
|
const mockMovies = [
|
||||||
|
{ id: 1, title: 'Movie 1', theMovieDbId: '12345' },
|
||||||
|
{ id: 2, title: 'Movie 2', theMovieDbId: '67890' }
|
||||||
|
];
|
||||||
|
const mockTvShows = [{ id: 3, title: 'Show 1', theTvDbId: '11111' }];
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/movie')
|
||||||
|
.reply(200, mockMovies);
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.get('/api/v1/Request/tv')
|
||||||
|
.reply(200, mockTvShows);
|
||||||
|
|
||||||
|
const retriever = new OmbiRetriever(instanceConfig);
|
||||||
|
await retriever.refreshCache();
|
||||||
|
|
||||||
|
const stats = retriever.getCacheStats();
|
||||||
|
|
||||||
|
expect(stats.movieRequests).toBe(2);
|
||||||
|
expect(stats.tvRequests).toBe(1);
|
||||||
|
expect(stats.movieMapSize).toBe(2);
|
||||||
|
expect(stats.tvMapSize).toBe(1);
|
||||||
|
expect(stats.lastFetch).toBeGreaterThan(0);
|
||||||
|
expect(stats.age).toBeGreaterThanOrEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* because misconfigured instances silently return no data rather than crashing.
|
* because misconfigured instances silently return no data rather than crashing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { parseInstances, getSonarrInstances, getRadarrInstances } from '../../server/utils/config.js';
|
import { parseInstances, getSonarrInstances, getRadarrInstances, getOmbiInstances } from '../../server/utils/config.js';
|
||||||
|
|
||||||
describe('parseInstances', () => {
|
describe('parseInstances', () => {
|
||||||
describe('JSON array format', () => {
|
describe('JSON array format', () => {
|
||||||
@@ -106,4 +106,87 @@ describe('parseInstances', () => {
|
|||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Ombi configuration', () => {
|
||||||
|
it('getOmbiInstances parses OMBI_INSTANCES JSON array', () => {
|
||||||
|
process.env.OMBI_INSTANCES = JSON.stringify([{ name: 'ombi-main', url: 'https://ombi.local', apiKey: 'ombi-key-123' }]);
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].name).toBe('ombi-main');
|
||||||
|
expect(result[0].url).toBe('https://ombi.local');
|
||||||
|
expect(result[0].apiKey).toBe('ombi-key-123');
|
||||||
|
expect(result[0].id).toBe('ombi-main');
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOmbiInstances parses multiple Ombi instances', () => {
|
||||||
|
process.env.OMBI_INSTANCES = JSON.stringify([
|
||||||
|
{ name: 'ombi-primary', url: 'https://ombi1.local', apiKey: 'key1' },
|
||||||
|
{ name: 'ombi-backup', url: 'https://ombi2.local', apiKey: 'key2' }
|
||||||
|
]);
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
expect(result[0].name).toBe('ombi-primary');
|
||||||
|
expect(result[1].name).toBe('ombi-backup');
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOmbiInstances falls back to legacy OMBI_URL and OMBI_API_KEY', () => {
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
process.env.OMBI_URL = 'https://legacy-ombi.local';
|
||||||
|
process.env.OMBI_API_KEY = 'legacy-ombi-key';
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].id).toBe('default');
|
||||||
|
expect(result[0].name).toBe('Default');
|
||||||
|
expect(result[0].url).toBe('https://legacy-ombi.local');
|
||||||
|
expect(result[0].apiKey).toBe('legacy-ombi-key');
|
||||||
|
delete process.env.OMBI_URL;
|
||||||
|
delete process.env.OMBI_API_KEY;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOmbiInstances returns empty array when not configured', () => {
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
delete process.env.OMBI_URL;
|
||||||
|
delete process.env.OMBI_API_KEY;
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOmbiInstances handles multi-line JSON', () => {
|
||||||
|
const json = `[
|
||||||
|
{
|
||||||
|
"name": "ombi-test",
|
||||||
|
"url": "https://ombi.test",
|
||||||
|
"apiKey": "test-key"
|
||||||
|
}
|
||||||
|
]`;
|
||||||
|
process.env.OMBI_INSTANCES = json;
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].name).toBe('ombi-test');
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOmbiInstances handles invalid JSON by falling back to legacy', () => {
|
||||||
|
process.env.OMBI_INSTANCES = 'not-valid-json';
|
||||||
|
process.env.OMBI_URL = 'https://fallback-ombi.local';
|
||||||
|
process.env.OMBI_API_KEY = 'fallback-key';
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].url).toBe('https://fallback-ombi.local');
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
delete process.env.OMBI_URL;
|
||||||
|
delete process.env.OMBI_API_KEY;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseInstances validates Ombi instance URLs', () => {
|
||||||
|
process.env.OMBI_INSTANCES = JSON.stringify([{ name: 'bad-url', url: 'not-a-valid-url', apiKey: 'key' }]);
|
||||||
|
const result = getOmbiInstances();
|
||||||
|
// Should still parse but with validation warning
|
||||||
|
expect(result).toHaveLength(1);
|
||||||
|
expect(result[0].url).toBe('not-a-valid-url');
|
||||||
|
delete process.env.OMBI_INSTANCES;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -752,4 +752,78 @@ describe('DownloadAssembler', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getOmbiLink', () => {
|
||||||
|
it('returns correct URL for valid requestId, type, and baseUrl', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink(123, 'movie', 'http://localhost:5000');
|
||||||
|
expect(result).toBe('http://localhost:5000/#/request/movie/123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct URL for TV type', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink(456, 'tv', 'http://localhost:5000');
|
||||||
|
expect(result).toBe('http://localhost:5000/#/request/tv/456');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when requestId is missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink(null, 'movie', 'http://localhost:5000');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when type is missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink(123, null, 'http://localhost:5000');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when baseUrl is missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink(123, 'movie', null);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when all parameters are missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink(null, null, null);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles string requestId', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiLink('abc-123', 'movie', 'http://localhost:5000');
|
||||||
|
expect(result).toBe('http://localhost:5000/#/request/movie/abc-123');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getOmbiSearchLink', () => {
|
||||||
|
it('returns correct URL for series type', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink(789, 'series', 'http://localhost:5000');
|
||||||
|
expect(result).toBe('http://localhost:5000/#/tv/search/789');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct URL for movie type', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink(101, 'movie', 'http://localhost:5000');
|
||||||
|
expect(result).toBe('http://localhost:5000/#/movie/search/101');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when searchId is missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink(null, 'series', 'http://localhost:5000');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when type is missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink(789, null, 'http://localhost:5000');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when baseUrl is missing', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink(789, 'series', null);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null for invalid type', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink(789, 'invalid', 'http://localhost:5000');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles string searchId', () => {
|
||||||
|
const result = DownloadAssembler.getOmbiSearchLink('search-123', 'movie', 'http://localhost:5000');
|
||||||
|
expect(result).toBe('http://localhost:5000/#/movie/search/search-123');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,368 @@
|
|||||||
|
// 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user