// #region MaintenanceStoreTests [C:2] [TYPE Module] [SEMANTICS test, store, maintenance] // @BRIEF Unit tests for MaintenanceStore runes-based store. // @RELATION DEPENDS_ON -> [MaintenanceStore] import { describe, it, expect, vi, beforeEach } from 'vitest'; // Use vi.hoisted to create mock functions before the mock factories const { mockListEvents, mockGetSettings, mockUpdateSettings, mockEndMaintenance, mockEndAllMaintenance, mockListDashboardBanners } = vi.hoisted(() => ({ mockListEvents: vi.fn(), mockGetSettings: vi.fn(), mockUpdateSettings: vi.fn(), mockEndMaintenance: vi.fn(), mockEndAllMaintenance: vi.fn(), mockListDashboardBanners: vi.fn(), })); // Mock toasts vi.mock('$lib/toasts.svelte.js', () => ({ addToast: vi.fn() })); // Mock i18n vi.mock('$lib/i18n/index.svelte.js', () => ({ t: { maintenance: {} } })); vi.mock('$lib/api/maintenance.js', () => ({ listEvents: mockListEvents, getSettings: mockGetSettings, updateSettings: mockUpdateSettings, endMaintenance: mockEndMaintenance, endAllMaintenance: mockEndAllMaintenance, listDashboardBanners: mockListDashboardBanners, })); import { createMaintenanceStore } from '$lib/stores/maintenance.svelte.js'; describe('MaintenanceStore', () => { let store; beforeEach(() => { vi.clearAllMocks(); mockListEvents.mockResolvedValue({ active: [], completed: [] }); mockGetSettings.mockResolvedValue({ target_environment_id: 'prod', display_timezone: 'UTC', banner_template: 'test template', dashboard_scope: 'published_only', excluded_dashboard_ids: [], forced_dashboard_ids: [], updated_at: null }); mockListDashboardBanners.mockResolvedValue([]); store = createMaintenanceStore(); }); it('initializes with empty state', () => { expect(store.activeEvents).toEqual([]); expect(store.completedEvents).toEqual([]); expect(store.settings).toBeNull(); expect(store.dashboardBanners).toEqual([]); expect(store.isLoading).toBe(false); expect(store.error).toBeNull(); }); it('loadEvents calls API and updates state', async () => { mockListEvents.mockResolvedValue({ active: [{ id: 'ev-1', tables: ['raw.sales'], status: 'active' }], completed: [{ id: 'ev-2', tables: ['raw.inventory'], status: 'completed' }] }); await store.loadEvents(); expect(mockListEvents).toHaveBeenCalledTimes(1); expect(store.activeEvents).toHaveLength(1); expect(store.activeEvents[0].id).toBe('ev-1'); expect(store.completedEvents).toHaveLength(1); expect(store.completedEvents[0].id).toBe('ev-2'); }); it('loadEvents sets error on API failure', async () => { mockListEvents.mockRejectedValue(new Error('Network error')); await store.loadEvents(); expect(store.error).toBeTruthy(); expect(store.activeEvents).toEqual([]); }); it('loadSettings calls API and updates settings', async () => { const testSettings = { target_environment_id: 'staging', display_timezone: 'Europe/Moscow', banner_template: 'custom template', dashboard_scope: 'all', excluded_dashboard_ids: [1, 2], forced_dashboard_ids: [3], updated_at: null }; mockGetSettings.mockResolvedValue(testSettings); await store.loadSettings(); expect(store.settings).toEqual(testSettings); }); it('loadSettings does not throw on API failure', async () => { mockGetSettings.mockRejectedValue(new Error('API error')); await expect(store.loadSettings()).resolves.not.toThrow(); expect(store.settings).toBeNull(); }); it('loadDashboardBanners updates state', async () => { const banners = [ { dashboard_id: 42, active: true, events: ['ev-1'], start_time: '2026-05-21T22:00:00Z', end_time: null }, { dashboard_id: 57, active: false, events: [], start_time: null, end_time: null } ]; mockListDashboardBanners.mockResolvedValue(banners); await store.loadDashboardBanners(); expect(mockListDashboardBanners).toHaveBeenCalledTimes(1); expect(store.dashboardBanners).toHaveLength(2); }); it('loadDashboardBanners handles non-array response', async () => { mockListDashboardBanners.mockResolvedValue(null); await store.loadDashboardBanners(); expect(store.dashboardBanners).toEqual([]); }); it('endEvent calls API and refreshes events', async () => { mockEndMaintenance.mockResolvedValue({ task_id: 'task-1', status: 'pending' }); mockListEvents.mockResolvedValue({ active: [], completed: [] }); await store.endEvent('ev-1'); expect(mockEndMaintenance).toHaveBeenCalledWith('ev-1'); expect(mockListEvents).toHaveBeenCalled(); }); it('endAllEvents calls API and refreshes events', async () => { mockEndAllMaintenance.mockResolvedValue({ task_id: 'task-bulk', status: 'pending' }); mockListEvents.mockResolvedValue({ active: [], completed: [] }); await store.endAllEvents(); expect(mockEndAllMaintenance).toHaveBeenCalled(); expect(mockListEvents).toHaveBeenCalled(); }); it('updateSettings calls API and updates settings', async () => { const newSettings = { dashboard_scope: 'all' }; mockUpdateSettings.mockResolvedValue({ ...newSettings, target_environment_id: 'prod', display_timezone: 'UTC', banner_template: 'test', excluded_dashboard_ids: [], forced_dashboard_ids: [], updated_at: '2026-05-22T10:00:00Z' }); await store.updateSettings(newSettings); expect(mockUpdateSettings).toHaveBeenCalledWith(newSettings); expect(store.settings).toBeTruthy(); expect(store.settings.dashboard_scope).toBe('all'); }); }); // #endregion MaintenanceStoreTests