Files
ss-tools/frontend/tests/maintenance-store.test.ts
busya 31680a1bc9 fix(maintenance): auto-expiry + adaptive markdown height + tests
- Auto-expiry: expired events auto-end on GET /events via task manager
- Height: _estimate_markdown_height adapted to unit=8px scale with padding detection
- CRITICAL-1: removed dead import remove_chart_from_position (QA finding)
- CRITICAL-2: added chart alive check in ensure_banner_chart (QA finding)
- CRITICAL-3: fixed test assertions update_markdown_chart → update_banner_on_dashboard
- @POST contract: fixed return range [2,12] → [19,200]
- Tests: 8 new tests (auto-expiry + layout height)
2026-05-25 08:36:33 +03:00

175 lines
6.0 KiB
TypeScript

// #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.js', () => ({
addToast: vi.fn()
}));
// Mock i18n
vi.mock('$lib/i18n', () => ({
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