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
193 lines
7.4 KiB
JavaScript
193 lines
7.4 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
/**
|
|
* Tests for server/utils/config.js
|
|
*
|
|
* Verifies that instance config is parsed correctly from both the modern JSON
|
|
* array format and the legacy single-instance env var format. This is critical
|
|
* because misconfigured instances silently return no data rather than crashing.
|
|
*/
|
|
|
|
import { parseInstances, getSonarrInstances, getRadarrInstances, getOmbiInstances } from '../../server/utils/config.js';
|
|
|
|
describe('parseInstances', () => {
|
|
describe('JSON array format', () => {
|
|
it('parses a valid single-instance JSON array', () => {
|
|
const json = JSON.stringify([{ name: 'main', url: 'https://sonarr.local', apiKey: 'abc123' }]);
|
|
const result = parseInstances(json, null, null);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].url).toBe('https://sonarr.local');
|
|
expect(result[0].apiKey).toBe('abc123');
|
|
});
|
|
|
|
it('parses multiple instances', () => {
|
|
const json = JSON.stringify([
|
|
{ name: 'main', url: 'https://s1.local', apiKey: 'key1' },
|
|
{ name: 'backup', url: 'https://s2.local', apiKey: 'key2' }
|
|
]);
|
|
const result = parseInstances(json, null, null);
|
|
expect(result).toHaveLength(2);
|
|
expect(result[1].name).toBe('backup');
|
|
});
|
|
|
|
it('adds id from name when present', () => {
|
|
const json = JSON.stringify([{ name: 'i3omb', url: 'https://s.local', apiKey: 'k' }]);
|
|
const result = parseInstances(json, null, null);
|
|
expect(result[0].id).toBe('i3omb');
|
|
});
|
|
|
|
it('generates fallback id when name is absent', () => {
|
|
const json = JSON.stringify([{ url: 'https://s.local', apiKey: 'k' }]);
|
|
const result = parseInstances(json, null, null);
|
|
expect(result[0].id).toBe('instance-1');
|
|
});
|
|
|
|
it('handles multi-line JSON by stripping whitespace', () => {
|
|
const json = `[
|
|
{
|
|
"name": "main",
|
|
"url": "https://sonarr.local",
|
|
"apiKey": "abc"
|
|
}
|
|
]`;
|
|
const result = parseInstances(json, null, null);
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
it('returns empty array for empty JSON array', () => {
|
|
expect(parseInstances('[]', null, null)).toEqual([]);
|
|
});
|
|
|
|
it('falls back to legacy format when JSON is malformed', () => {
|
|
const result = parseInstances('not-json', 'https://legacy.local', 'legacyKey');
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].url).toBe('https://legacy.local');
|
|
});
|
|
});
|
|
|
|
describe('legacy single-instance format', () => {
|
|
it('returns single instance from legacy URL + key', () => {
|
|
const result = parseInstances(null, 'https://sonarr.local', 'legacyapikey');
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].id).toBe('default');
|
|
expect(result[0].name).toBe('Default');
|
|
expect(result[0].url).toBe('https://sonarr.local');
|
|
expect(result[0].apiKey).toBe('legacyapikey');
|
|
});
|
|
|
|
it('returns empty array for qBittorrent with no apiKey and no JSON (legacy requires key)', () => {
|
|
// parseInstances requires legacyKey to be truthy for the legacy path;
|
|
// qBittorrent uses JSON array format, not the legacy URL+key path.
|
|
const result = parseInstances(null, 'https://qbt.local', null, 'admin', 'pass123');
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('returns empty array when both JSON and legacy URL are missing', () => {
|
|
expect(parseInstances(null, null, null)).toEqual([]);
|
|
});
|
|
|
|
it('returns empty array when URL is set but key is missing', () => {
|
|
expect(parseInstances(null, 'https://sonarr.local', null)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('env-based getters', () => {
|
|
it('getSonarrInstances reads SONARR_INSTANCES from env', () => {
|
|
process.env.SONARR_INSTANCES = JSON.stringify([{ name: 'test', url: 'https://s.local', apiKey: 'k' }]);
|
|
const result = getSonarrInstances();
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].name).toBe('test');
|
|
delete process.env.SONARR_INSTANCES;
|
|
});
|
|
|
|
it('getRadarrInstances returns empty array when unconfigured', () => {
|
|
delete process.env.RADARR_INSTANCES;
|
|
delete process.env.RADARR_URL;
|
|
const result = getRadarrInstances();
|
|
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;
|
|
});
|
|
});
|
|
});
|