Files
sofarr/tests/unit/clients/PollingRadarrRetriever.test.js
gronod 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
test: remediate test suite, enable skipped frontend/SSE tests, and add comprehensive unit tests
- 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
2026-05-22 13:33:21 +01:00

199 lines
5.8 KiB
JavaScript

// Copyright (c) 2026 Gordon Bolton. MIT License.
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import nock from 'nock';
import PollingRadarrRetriever from '../../../server/clients/PollingRadarrRetriever';
describe('PollingRadarrRetriever', () => {
const config = {
id: 'radarr-test',
name: 'Test Radarr',
url: 'http://radarr-mock.test',
apiKey: 'mock-api-key'
};
let retriever;
beforeEach(() => {
retriever = new PollingRadarrRetriever(config);
nock.disableNetConnect();
});
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});
it('should return correct type and instance ID', () => {
expect(retriever.getRetrieverType()).toBe('radarr');
expect(retriever.getInstanceId()).toBe('radarr-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: 'Movie 1' },
{ id: 2, title: 'Movie 2' }
]
};
nock(config.url)
.get('/api/v3/queue')
.query({ includeMovie: '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: `Movie ${i}` }));
const page2Records = [{ id: 1000, title: 'Movie 1000' }];
nock(config.url)
.get('/api/v3/queue')
.query({ includeMovie: 'true', page: 1, pageSize: 1000 })
.reply(200, {
page: 1,
pageSize: 1000,
totalRecords: 1001,
records: page1Records
});
nock(config.url)
.get('/api/v3/queue')
.query({ includeMovie: '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, includeMovie: '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,
includeMovie: 'false',
sortKey: 'date',
sortDir: 'descending',
startDate: '2026-05-22T00:00:00Z'
})
.reply(200, mockHistoryResponse);
const history = await retriever.getHistory({
pageSize: 10,
includeMovie: 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, includeMovie: 'true' })
.reply(200, {
page: 1,
pageSize: 50,
records: page1Records
});
nock(config.url)
.get('/api/v3/history')
.query({ page: 2, pageSize: 50, includeMovie: '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();
});
});
});