aec04474be
- Add tests/unit/utils/poller.test.js covering background polling lock, registry, error recovery, webhook bypasses, and global fallbacks - Add tests/integration/rateLimiter.test.js verifying 429 response rate-limiting in an isolated production environment - Add tests/integration/ombiDecoration.test.js covering deep links and admin role checks - Expand tests/frontend/ui/downloads.test.js covering createServiceIcons() and createClientLogo() fallbacks - Expand tests/integration/dashboard.test.js verifying SSE heartbeats, payload schema contract, and listener cleanup on client disconnect
143 lines
4.4 KiB
JavaScript
143 lines
4.4 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import nock from 'nock';
|
|
import { createRequire } from 'module';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const { decorateDownloadsWithArrLinks } = require('../../server/utils/ombiHelpers.js');
|
|
const arrRetrieverRegistry = require('../../server/utils/arrRetrievers.js');
|
|
|
|
const SONARR_BASE = 'https://sonarr-decor.test';
|
|
const RADARR_BASE = 'https://radarr-decor.test';
|
|
|
|
describe('decorateDownloadsWithArrLinks Integration Tests', () => {
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
nock.cleanAll();
|
|
|
|
// Reset the singleton retrievers registry so we can inject our test instances
|
|
arrRetrieverRegistry.retrievers.clear();
|
|
arrRetrieverRegistry.initialized = false;
|
|
|
|
// Configure test environment variables for retrievers
|
|
process.env.SONARR_INSTANCES = JSON.stringify([
|
|
{ id: 'sonarr-1', name: 'Test Sonarr', url: SONARR_BASE, apiKey: 'sonarr-key' }
|
|
]);
|
|
process.env.RADARR_INSTANCES = JSON.stringify([
|
|
{ id: 'radarr-1', name: 'Test Radarr', url: RADARR_BASE, apiKey: 'radarr-key' }
|
|
]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
nock.cleanAll();
|
|
delete process.env.SONARR_INSTANCES;
|
|
delete process.env.RADARR_INSTANCES;
|
|
arrRetrieverRegistry.retrievers.clear();
|
|
arrRetrieverRegistry.initialized = false;
|
|
});
|
|
|
|
it('decorates a series download with Sonarr link matching on title', async () => {
|
|
// Mock Sonarr series query
|
|
nock(SONARR_BASE)
|
|
.get('/api/v3/series')
|
|
.reply(200, [
|
|
{ id: 42, title: 'The Mandalorian', titleSlug: 'the-mandalorian' }
|
|
]);
|
|
|
|
// Mock Radarr movie query (empty)
|
|
nock(RADARR_BASE)
|
|
.get('/api/v3/movie')
|
|
.reply(200, []);
|
|
|
|
const downloads = [
|
|
{
|
|
title: 'The.Mandalorian.S01E01.1080p',
|
|
type: 'series',
|
|
seriesName: 'The Mandalorian',
|
|
arrSeriesId: null
|
|
}
|
|
];
|
|
|
|
await decorateDownloadsWithArrLinks(downloads, true);
|
|
|
|
expect(downloads[0].arrLink).toBe(`${SONARR_BASE}/series/the-mandalorian`);
|
|
expect(downloads[0].arrType).toBe('sonarr');
|
|
});
|
|
|
|
it('decorates a movie download with Radarr link matching on content ID', async () => {
|
|
// Mock Sonarr series query (empty)
|
|
nock(SONARR_BASE)
|
|
.get('/api/v3/series')
|
|
.reply(200, []);
|
|
|
|
// Mock Radarr movie query with matching ID
|
|
nock(RADARR_BASE)
|
|
.get('/api/v3/movie')
|
|
.reply(200, [
|
|
{ id: 99, title: 'Blade Runner 2049', titleSlug: 'blade-runner-2049' }
|
|
]);
|
|
|
|
const downloads = [
|
|
{
|
|
title: 'Blade.Runner.2049.2017.1080p',
|
|
type: 'movie',
|
|
movieName: 'Blade Runner 2049',
|
|
arrInstanceUrl: RADARR_BASE,
|
|
arrContentId: 99
|
|
}
|
|
];
|
|
|
|
await decorateDownloadsWithArrLinks(downloads, true);
|
|
|
|
expect(downloads[0].arrLink).toBe(`${RADARR_BASE}/movie/blade-runner-2049`);
|
|
expect(downloads[0].arrType).toBe('radarr');
|
|
});
|
|
|
|
it('skips decoration entirely when isAdmin is false', async () => {
|
|
const downloads = [
|
|
{
|
|
title: 'The.Mandalorian.S01E01.1080p',
|
|
type: 'series',
|
|
seriesName: 'The Mandalorian'
|
|
}
|
|
];
|
|
|
|
// No nocks are set up, so any HTTP calls would throw or error
|
|
await decorateDownloadsWithArrLinks(downloads, false);
|
|
|
|
expect(downloads[0].arrLink).toBeUndefined();
|
|
expect(downloads[0].arrType).toBeUndefined();
|
|
});
|
|
|
|
it('handles empty downloads array gracefully', async () => {
|
|
// No mock setups needed, should complete without throwing
|
|
await expect(decorateDownloadsWithArrLinks([], true)).resolves.not.toThrow();
|
|
});
|
|
|
|
it('handles external API fetch failures gracefully without failing the decoration pipeline', async () => {
|
|
// Mock Sonarr series query throwing connection error
|
|
nock(SONARR_BASE)
|
|
.get('/api/v3/series')
|
|
.replyWithError('connection refused');
|
|
|
|
// Mock Radarr movie query throwing timeout error
|
|
nock(RADARR_BASE)
|
|
.get('/api/v3/movie')
|
|
.replyWithError('timeout');
|
|
|
|
const downloads = [
|
|
{
|
|
title: 'The.Mandalorian.S01E01.1080p',
|
|
type: 'series',
|
|
seriesName: 'The Mandalorian'
|
|
}
|
|
];
|
|
|
|
await expect(decorateDownloadsWithArrLinks(downloads, true)).resolves.not.toThrow();
|
|
|
|
// No links decorated since the fetch failed
|
|
expect(downloads[0].arrLink).toBeUndefined();
|
|
expect(downloads[0].arrType).toBeUndefined();
|
|
});
|
|
});
|