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
321 lines
10 KiB
JavaScript
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!');
|
|
});
|
|
});
|