Files
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

321 lines
10 KiB
JavaScript

// Copyright (c) 2026 Gordon Bolton. MIT License.
/**
* @vitest-environment jsdom
* Tests for client/src/state.js
*
* Verifies the structure and initial values of the state object.
* This ensures the Ombi-related state fields are properly defined.
*/
import { state } from '../../client/src/state.js';
describe('state object', () => {
it('has ombiBaseUrl field initialized to null', () => {
expect(state).toHaveProperty('ombiBaseUrl');
expect(state.ombiBaseUrl).toBeNull();
});
it('has ombiRequests field initialized to null', () => {
expect(state).toHaveProperty('ombiRequests');
expect(state.ombiRequests).toBeNull();
});
it('has ombiWebhook field with correct structure', () => {
expect(state).toHaveProperty('ombiWebhook');
expect(state.ombiWebhook).toEqual({
enabled: false,
triggers: {
requestAvailable: false,
requestApproved: false,
requestDeclined: false,
requestPending: false,
requestProcessing: false
},
stats: null
});
});
it('has ombiWebhook triggers with all required fields', () => {
const { triggers } = state.ombiWebhook;
expect(triggers).toHaveProperty('requestAvailable');
expect(triggers).toHaveProperty('requestApproved');
expect(triggers).toHaveProperty('requestDeclined');
expect(triggers).toHaveProperty('requestPending');
expect(triggers).toHaveProperty('requestProcessing');
});
it('has all Ombi trigger fields initialized to false', () => {
const { triggers } = state.ombiWebhook;
expect(triggers.requestAvailable).toBe(false);
expect(triggers.requestApproved).toBe(false);
expect(triggers.requestDeclined).toBe(false);
expect(triggers.requestPending).toBe(false);
expect(triggers.requestProcessing).toBe(false);
});
it('has ombiWebhook stats initialized to null', () => {
expect(state.ombiWebhook.stats).toBeNull();
});
it('has ombiWebhook enabled initialized to false', () => {
expect(state.ombiWebhook.enabled).toBe(false);
});
});
// ---------------------------------------------------------------------------
// Enabled tests with robust mocking
// ---------------------------------------------------------------------------
import { enableOmbiWebhook as apiEnableOmbiWebhook, testOmbiWebhook as apiTestOmbiWebhook } from '../../client/src/api.js';
import { renderWebhookStatus, enableOmbiWebhook as uiEnableOmbiWebhook, testOmbiWebhook as uiTestOmbiWebhook } from '../../client/src/ui/webhooks.js';
const mockFetch = vi.fn().mockImplementation((url, init) => {
if (url === '/api/ombi/webhook/enable') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ success: true })
});
}
if (url === '/api/ombi/webhook/test') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ success: true })
});
}
if (url === '/api/webhook/config') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ valid: true })
});
}
if (url === '/api/sonarr/notifications') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([])
});
}
if (url === '/api/radarr/notifications') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([])
});
}
if (url === '/api/ombi/webhook/status') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
enabled: true,
triggers: {
requestAvailable: true,
requestApproved: true,
requestDeclined: true,
requestPending: true,
requestProcessing: true
},
stats: {
eventsReceived: 10,
pollsSkipped: 5,
lastWebhookTimestamp: Date.now() - 60000
}
})
});
}
if (url === '/api/webhook/metrics') {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({})
});
}
return Promise.resolve({
ok: false,
status: 404
});
});
function setupDomForOmbiWebhooks() {
document.body.innerHTML = `
<div id="webhooks-section"></div>
<div id="webhooks-content"></div>
<div id="webhooks-toggle"></div>
<div id="webhook-loading" class="hidden"></div>
<div id="sonarr-status"></div>
<button id="enable-sonarr-webhook"></button>
<button id="test-sonarr-webhook"></button>
<div id="sonarr-triggers"></div>
<div id="sonarr-stats"></div>
<div id="radarr-status"></div>
<button id="enable-radarr-webhook"></button>
<button id="test-radarr-webhook"></button>
<div id="radarr-triggers"></div>
<div id="radarr-stats"></div>
<div id="ombi-status"></div>
<button id="enable-ombi-webhook"></button>
<button id="test-ombi-webhook"></button>
<div id="ombi-triggers" class="hidden">
<div id="ombi-requestAvailable"></div>
<div id="ombi-requestApproved"></div>
<div id="ombi-requestDeclined"></div>
<div id="ombi-requestPending"></div>
<div id="ombi-requestProcessing"></div>
</div>
<div id="ombi-stats" class="hidden">
<div id="ombi-events"></div>
<div id="ombi-polls"></div>
<div id="ombi-last"></div>
</div>
`;
}
describe('frontend API functions (enableOmbiWebhook, testOmbiWebhook)', () => {
beforeEach(() => {
global.fetch = mockFetch;
mockFetch.mockClear();
state.csrfToken = 'test-csrf-token';
});
afterEach(() => {
delete global.fetch;
});
it('enableOmbiWebhook makes POST request to /api/ombi/webhook/enable', async () => {
const result = await apiEnableOmbiWebhook();
expect(mockFetch).toHaveBeenCalledWith('/api/ombi/webhook/enable', {
method: 'POST',
headers: { 'X-CSRF-Token': 'test-csrf-token' }
});
expect(result).toEqual({ success: true });
});
it('testOmbiWebhook makes POST request to /api/ombi/webhook/test', async () => {
const result = await apiTestOmbiWebhook();
expect(mockFetch).toHaveBeenCalledWith('/api/ombi/webhook/test', {
method: 'POST',
headers: { 'X-CSRF-Token': 'test-csrf-token' }
});
expect(result).toEqual({ success: true });
});
});
describe('frontend UI functions (webhooks.js Ombi functions)', () => {
beforeEach(() => {
global.fetch = mockFetch;
mockFetch.mockClear();
global.alert = vi.fn();
setupDomForOmbiWebhooks();
state.csrfToken = 'test-csrf-token';
// Set up default state for Ombi webhook
state.ombiWebhook = {
enabled: false,
triggers: {
requestAvailable: false,
requestApproved: false,
requestDeclined: false,
requestPending: false,
requestProcessing: false
},
stats: null
};
});
afterEach(() => {
delete global.fetch;
delete global.alert;
document.body.innerHTML = '';
});
it('renderWebhookStatus renders Ombi webhook status correctly', () => {
// 1. Test disabled state
state.ombiWebhook.enabled = false;
renderWebhookStatus();
expect(document.getElementById('ombi-status').textContent).toBe('○ Disabled');
expect(document.getElementById('enable-ombi-webhook').classList.contains('hidden')).toBe(false);
expect(document.getElementById('test-ombi-webhook').classList.contains('hidden')).toBe(true);
expect(document.getElementById('ombi-triggers').classList.contains('hidden')).toBe(true);
// 2. Test enabled state with triggers and stats
state.ombiWebhook.enabled = true;
state.ombiWebhook.triggers.requestAvailable = true;
state.ombiWebhook.triggers.requestApproved = true;
state.ombiWebhook.stats = {
eventsReceived: 42,
pollsSkipped: 17,
lastWebhookTimestamp: Date.now() - 3600000 // 1 hour ago
};
renderWebhookStatus();
expect(document.getElementById('ombi-status').textContent).toBe('● Enabled');
expect(document.getElementById('enable-ombi-webhook').classList.contains('hidden')).toBe(true);
expect(document.getElementById('test-ombi-webhook').classList.contains('hidden')).toBe(false);
expect(document.getElementById('ombi-triggers').classList.contains('hidden')).toBe(false);
// Check triggers rendering
expect(document.getElementById('ombi-requestAvailable').textContent).toBe('✓');
expect(document.getElementById('ombi-requestApproved').textContent).toBe('✓');
expect(document.getElementById('ombi-requestDeclined').textContent).toBe('✗');
// Check stats rendering
expect(document.getElementById('ombi-stats').classList.contains('hidden')).toBe(false);
expect(document.getElementById('ombi-events').textContent).toBe('42');
expect(document.getElementById('ombi-polls').textContent).toBe('17');
expect(document.getElementById('ombi-last').textContent).toBe('1h ago');
});
it('enableOmbiWebhook UI handler calls API and updates state', async () => {
// Mock the state returned by fetchWebhookStatus to enable it
mockFetch.mockImplementation((url) => {
if (url === '/api/ombi/webhook/enable') {
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true }) });
}
if (url === '/api/ombi/webhook/status') {
// Return updated state where it is enabled
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
enabled: true,
triggers: {
requestAvailable: true,
requestApproved: false,
requestDeclined: false,
requestPending: false,
requestProcessing: false
},
stats: null
})
});
}
// For all other config fetches, return basic values
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
});
await uiEnableOmbiWebhook();
// Should make POST call to enable
expect(mockFetch).toHaveBeenCalledWith('/api/ombi/webhook/enable', {
method: 'POST',
headers: { 'X-CSRF-Token': 'test-csrf-token' }
});
// State should be updated
expect(state.ombiWebhook.enabled).toBe(true);
// Render the webhook status to update the DOM
renderWebhookStatus();
// UI should show enabled status
expect(document.getElementById('ombi-status').textContent).toBe('● Enabled');
});
it('testOmbiWebhook UI handler calls API and updates state', async () => {
await uiTestOmbiWebhook();
// Should make POST call to test
expect(mockFetch).toHaveBeenCalledWith('/api/ombi/webhook/test', {
method: 'POST',
headers: { 'X-CSRF-Token': 'test-csrf-token' }
});
// Should alert success
expect(global.alert).toHaveBeenCalledWith('Ombi webhook test sent successfully!');
});
});