4aa3590017
Build and Push Docker Image / build (push) Successful in 53s
Docs Check / Markdown lint (push) Successful in 1m36s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 2m31s
CI / Security audit (push) Successful in 2m55s
Docs Check / Mermaid diagram parse check (push) Successful in 3m24s
CI / Swagger Validation & Coverage (push) Successful in 3m30s
CI / Tests & coverage (push) Successful in 3m50s
- Deleted redundant unit test file tests/unit/dashboard.test.js - Enabled skipped frontend DOM state and API tests in tests/frontend/state.test.js - Fixed Supertest client socket abort exception in SSE stream integration tests using new graceful testClose parameter - Consolidated duplicate helpers in server/routes/history.js and server/utils/arrRetrievers.js to unified services TagMatcher and DownloadAssembler - Added comprehensive unit tests for loadSecrets.js, PollingSonarrRetriever.js, PollingRadarrRetriever.js, and ombiHelpers.js - Achieved a 100% Vitest pass rate (834/834 tests) with robust code coverage
201 lines
6.0 KiB
JavaScript
201 lines
6.0 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import nock from 'nock';
|
|
import PollingSonarrRetriever from '../../../server/clients/PollingSonarrRetriever';
|
|
|
|
describe('PollingSonarrRetriever', () => {
|
|
const config = {
|
|
id: 'sonarr-test',
|
|
name: 'Test Sonarr',
|
|
url: 'http://sonarr-mock.test',
|
|
apiKey: 'mock-api-key'
|
|
};
|
|
|
|
let retriever;
|
|
|
|
beforeEach(() => {
|
|
retriever = new PollingSonarrRetriever(config);
|
|
nock.disableNetConnect();
|
|
});
|
|
|
|
afterEach(() => {
|
|
nock.cleanAll();
|
|
nock.enableNetConnect();
|
|
});
|
|
|
|
it('should return correct type and instance ID', () => {
|
|
expect(retriever.getRetrieverType()).toBe('sonarr');
|
|
expect(retriever.getInstanceId()).toBe('sonarr-test');
|
|
});
|
|
|
|
describe('getTags', () => {
|
|
it('should fetch tags successfully', async () => {
|
|
const mockTags = [{ id: 1, label: 'tag1' }, { id: 2, label: 'tag2' }];
|
|
nock(config.url)
|
|
.get('/api/v3/tag')
|
|
.matchHeader('X-Api-Key', config.apiKey)
|
|
.reply(200, mockTags);
|
|
|
|
const tags = await retriever.getTags();
|
|
expect(tags).toEqual(mockTags);
|
|
});
|
|
|
|
it('should return an empty array on error and log it', async () => {
|
|
nock(config.url)
|
|
.get('/api/v3/tag')
|
|
.reply(500, 'Internal Server Error');
|
|
|
|
const tags = await retriever.getTags();
|
|
expect(tags).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('getQueue', () => {
|
|
it('should fetch queue in a single page if records count is less than 1000', async () => {
|
|
const mockQueueResponse = {
|
|
page: 1,
|
|
pageSize: 1000,
|
|
totalRecords: 2,
|
|
records: [
|
|
{ id: 1, title: 'Item 1' },
|
|
{ id: 2, title: 'Item 2' }
|
|
]
|
|
};
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/queue')
|
|
.query({ includeSeries: 'true', includeEpisode: 'true', page: 1, pageSize: 1000 })
|
|
.matchHeader('X-Api-Key', config.apiKey)
|
|
.reply(200, mockQueueResponse);
|
|
|
|
const queue = await retriever.getQueue();
|
|
expect(queue.records).toHaveLength(2);
|
|
expect(queue.records).toEqual(mockQueueResponse.records);
|
|
});
|
|
|
|
it('should paginate queue if the page size is exactly 1000', async () => {
|
|
const page1Records = Array.from({ length: 1000 }, (_, i) => ({ id: i, title: `Item ${i}` }));
|
|
const page2Records = [{ id: 1000, title: 'Item 1000' }];
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/queue')
|
|
.query({ includeSeries: 'true', includeEpisode: 'true', page: 1, pageSize: 1000 })
|
|
.reply(200, {
|
|
page: 1,
|
|
pageSize: 1000,
|
|
totalRecords: 1001,
|
|
records: page1Records
|
|
});
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/queue')
|
|
.query({ includeSeries: 'true', includeEpisode: 'true', page: 2, pageSize: 1000 })
|
|
.reply(200, {
|
|
page: 2,
|
|
pageSize: 1000,
|
|
totalRecords: 1001,
|
|
records: page2Records
|
|
});
|
|
|
|
const queue = await retriever.getQueue();
|
|
expect(queue.records).toHaveLength(1001);
|
|
expect(queue.records[1000]).toEqual(page2Records[0]);
|
|
});
|
|
|
|
it('should throw an error if the request fails', async () => {
|
|
nock(config.url)
|
|
.get('/api/v3/queue')
|
|
.query(true)
|
|
.reply(500, 'Server Error');
|
|
|
|
await expect(retriever.getQueue()).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('getHistory', () => {
|
|
it('should fetch history with default parameters', async () => {
|
|
const mockHistoryResponse = {
|
|
page: 1,
|
|
pageSize: 100,
|
|
totalRecords: 2,
|
|
records: [
|
|
{ id: 1, eventType: 'grabbed' },
|
|
{ id: 2, eventType: 'downloadFolderImported' }
|
|
]
|
|
};
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/history')
|
|
.query({ page: 1, pageSize: 100, includeSeries: 'true', includeEpisode: 'true' })
|
|
.matchHeader('X-Api-Key', config.apiKey)
|
|
.reply(200, mockHistoryResponse);
|
|
|
|
const history = await retriever.getHistory();
|
|
expect(history.records).toHaveLength(2);
|
|
expect(history.records).toEqual(mockHistoryResponse.records);
|
|
});
|
|
|
|
it('should apply sorting and startDate filters from options', async () => {
|
|
const mockHistoryResponse = { page: 1, pageSize: 10, totalRecords: 0, records: [] };
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/history')
|
|
.query({
|
|
page: 1,
|
|
pageSize: 10,
|
|
includeSeries: 'false',
|
|
includeEpisode: 'false',
|
|
sortKey: 'date',
|
|
sortDir: 'descending',
|
|
startDate: '2026-05-22T00:00:00Z'
|
|
})
|
|
.reply(200, mockHistoryResponse);
|
|
|
|
const history = await retriever.getHistory({
|
|
pageSize: 10,
|
|
includeSeries: false,
|
|
includeEpisode: false,
|
|
sortKey: 'date',
|
|
sortDir: 'descending',
|
|
startDate: '2026-05-22T00:00:00Z'
|
|
});
|
|
expect(history.records).toEqual([]);
|
|
});
|
|
|
|
it('should paginate history when more pages are available up to maxPages', async () => {
|
|
const page1Records = Array.from({ length: 50 }, (_, i) => ({ id: i }));
|
|
const page2Records = Array.from({ length: 50 }, (_, i) => ({ id: 50 + i }));
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/history')
|
|
.query({ page: 1, pageSize: 50, includeSeries: 'true', includeEpisode: 'true' })
|
|
.reply(200, {
|
|
page: 1,
|
|
pageSize: 50,
|
|
records: page1Records
|
|
});
|
|
|
|
nock(config.url)
|
|
.get('/api/v3/history')
|
|
.query({ page: 2, pageSize: 50, includeSeries: 'true', includeEpisode: 'true' })
|
|
.reply(200, {
|
|
page: 2,
|
|
pageSize: 50,
|
|
records: page2Records
|
|
});
|
|
|
|
const history = await retriever.getHistory({ pageSize: 50, maxPages: 2 });
|
|
expect(history.records).toHaveLength(100);
|
|
});
|
|
|
|
it('should throw an error on API failure', async () => {
|
|
nock(config.url)
|
|
.get('/api/v3/history')
|
|
.query(true)
|
|
.reply(500, 'Server Error');
|
|
|
|
await expect(retriever.getHistory()).rejects.toThrow();
|
|
});
|
|
});
|
|
});
|