Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
216 lines
5.6 KiB
JavaScript
216 lines
5.6 KiB
JavaScript
/**
|
|
* Setup file for frontend tests with Vitest + jsdom.
|
|
*
|
|
* This file:
|
|
* - Configures the DOM environment
|
|
* - Mocks fetch API
|
|
* - Mocks WebSocket
|
|
* - Mocks localStorage
|
|
* - Provides utility functions for tests
|
|
*/
|
|
|
|
import { vi, beforeEach, afterEach } from 'vitest';
|
|
|
|
// ============================================================================
|
|
// MOCK: localStorage
|
|
// ============================================================================
|
|
|
|
const localStorageMock = (() => {
|
|
let store = {};
|
|
return {
|
|
getItem: vi.fn((key) => store[key] || null),
|
|
setItem: vi.fn((key, value) => {
|
|
store[key] = String(value);
|
|
}),
|
|
removeItem: vi.fn((key) => {
|
|
delete store[key];
|
|
}),
|
|
clear: vi.fn(() => {
|
|
store = {};
|
|
}),
|
|
get length() {
|
|
return Object.keys(store).length;
|
|
},
|
|
key: vi.fn((i) => Object.keys(store)[i] || null),
|
|
};
|
|
})();
|
|
|
|
Object.defineProperty(global, 'localStorage', {
|
|
value: localStorageMock,
|
|
writable: true,
|
|
});
|
|
|
|
// ============================================================================
|
|
// MOCK: fetch API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a mock fetch response.
|
|
* @param {object} data - Response data
|
|
* @param {number} status - HTTP status code
|
|
* @returns {Response} Mock Response object
|
|
*/
|
|
export function createMockResponse(data, status = 200) {
|
|
return {
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
statusText: status === 200 ? 'OK' : 'Error',
|
|
json: vi.fn().mockResolvedValue(data),
|
|
text: vi.fn().mockResolvedValue(JSON.stringify(data)),
|
|
headers: new Headers({ 'Content-Type': 'application/json' }),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Setup fetch mock with default responses.
|
|
* @param {object} responses - Map of URL patterns to responses
|
|
*/
|
|
export function setupFetchMock(responses = {}) {
|
|
const defaultResponses = {
|
|
'/api/auth/status': { setup_required: false, authenticated: true, user: { username: 'test' } },
|
|
'/api/config': { debug_mode: false },
|
|
'/api/hosts': [],
|
|
'/api/tasks': [],
|
|
'/api/schedules': [],
|
|
'/api/playbooks': [],
|
|
'/api/health': { status: 'healthy' },
|
|
...responses,
|
|
};
|
|
|
|
global.fetch = vi.fn((url, options = {}) => {
|
|
const urlPath = new URL(url, 'http://localhost').pathname;
|
|
|
|
for (const [pattern, response] of Object.entries(defaultResponses)) {
|
|
if (urlPath.includes(pattern) || urlPath === pattern) {
|
|
return Promise.resolve(createMockResponse(response));
|
|
}
|
|
}
|
|
|
|
// Default 404 for unmatched URLs
|
|
return Promise.resolve(createMockResponse({ detail: 'Not found' }, 404));
|
|
});
|
|
|
|
return global.fetch;
|
|
}
|
|
|
|
// ============================================================================
|
|
// MOCK: WebSocket
|
|
// ============================================================================
|
|
|
|
export class MockWebSocket {
|
|
static CONNECTING = 0;
|
|
static OPEN = 1;
|
|
static CLOSING = 2;
|
|
static CLOSED = 3;
|
|
|
|
constructor(url) {
|
|
this.url = url;
|
|
this.readyState = MockWebSocket.CONNECTING;
|
|
this.onopen = null;
|
|
this.onclose = null;
|
|
this.onmessage = null;
|
|
this.onerror = null;
|
|
this._messageQueue = [];
|
|
|
|
// Auto-connect after a tick
|
|
setTimeout(() => {
|
|
this.readyState = MockWebSocket.OPEN;
|
|
if (this.onopen) {
|
|
this.onopen({ type: 'open' });
|
|
}
|
|
}, 0);
|
|
}
|
|
|
|
send(data) {
|
|
if (this.readyState !== MockWebSocket.OPEN) {
|
|
throw new Error('WebSocket is not open');
|
|
}
|
|
this._messageQueue.push(data);
|
|
}
|
|
|
|
close(code = 1000, reason = '') {
|
|
this.readyState = MockWebSocket.CLOSED;
|
|
if (this.onclose) {
|
|
this.onclose({ type: 'close', code, reason });
|
|
}
|
|
}
|
|
|
|
// Test helper: simulate receiving a message
|
|
_receiveMessage(data) {
|
|
if (this.onmessage) {
|
|
this.onmessage({
|
|
type: 'message',
|
|
data: typeof data === 'string' ? data : JSON.stringify(data),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Test helper: simulate an error
|
|
_triggerError(error) {
|
|
if (this.onerror) {
|
|
this.onerror({ type: 'error', error });
|
|
}
|
|
}
|
|
}
|
|
|
|
global.WebSocket = MockWebSocket;
|
|
|
|
// ============================================================================
|
|
// DOM UTILITIES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a minimal DOM structure for testing.
|
|
*/
|
|
export function setupMinimalDOM() {
|
|
document.body.innerHTML = `
|
|
<div id="main-content">
|
|
<nav class="nav-sidebar"></nav>
|
|
<main class="main-content">
|
|
<div id="page-dashboard" class="page-section active"></div>
|
|
<div id="page-hosts" class="page-section"></div>
|
|
<div id="page-tasks" class="page-section"></div>
|
|
<div id="page-schedules" class="page-section"></div>
|
|
</main>
|
|
</div>
|
|
<div id="login-screen" class="hidden"></div>
|
|
<div id="setup-screen" class="hidden"></div>
|
|
<div id="notification-container"></div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Wait for DOM updates.
|
|
* @param {number} ms - Milliseconds to wait
|
|
*/
|
|
export function waitForDOM(ms = 0) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
// ============================================================================
|
|
// HOOKS
|
|
// ============================================================================
|
|
|
|
beforeEach(() => {
|
|
// Clear localStorage
|
|
localStorageMock.clear();
|
|
|
|
// Reset fetch mock
|
|
if (global.fetch) {
|
|
global.fetch.mockClear?.();
|
|
}
|
|
|
|
// Clear DOM
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
// ============================================================================
|
|
// EXPORTS
|
|
// ============================================================================
|
|
|
|
export { localStorageMock };
|