fix tax log

This commit is contained in:
2026-02-19 16:05:59 +03:00
parent 197647d97a
commit b40649b9ed
36 changed files with 4414 additions and 40 deletions

View File

@@ -0,0 +1,8 @@
// [DEF:environment:Mock]
// @PURPOSE: Mock for $app/environment in tests
export const browser = true;
export const dev = true;
export const building = false;
// [/DEF:environment:Mock]

View File

@@ -0,0 +1,10 @@
// [DEF:navigation:Mock]
// @PURPOSE: Mock for $app/navigation in tests
export const goto = () => Promise.resolve();
export const push = () => Promise.resolve();
export const replace = () => Promise.resolve();
export const prefetch = () => Promise.resolve();
export const prefetchRoutes = () => Promise.resolve();
// [/DEF:navigation:Mock]

View File

@@ -0,0 +1,23 @@
// [DEF:stores:Mock]
// @PURPOSE: Mock for $app/stores in tests
import { writable, readable } from 'svelte/store';
export const page = readable({
url: new URL('http://localhost'),
params: {},
route: { id: 'test' },
status: 200,
error: null,
data: {},
form: null
});
export const navigating = writable(null);
export const updated = {
check: () => Promise.resolve(false),
subscribe: writable(false).subscribe
};
// [/DEF:stores:Mock]

View File

@@ -0,0 +1,63 @@
// [DEF:setupTests:Module]
// @TIER: STANDARD
// @PURPOSE: Global test setup with mocks for SvelteKit modules
// @LAYER: UI
import { vi } from 'vitest';
// Mock $app/environment
vi.mock('$app/environment', () => ({
browser: true,
dev: true,
building: false
}));
// Mock $app/stores
vi.mock('$app/stores', () => {
const { writable } = require('svelte/store');
return {
page: writable({ url: new URL('http://localhost'), params: {}, route: { id: 'test' } }),
navigating: writable(null),
updated: { check: vi.fn(), subscribe: writable(false).subscribe }
};
});
// Mock $app/navigation
vi.mock('$app/navigation', () => ({
goto: vi.fn(),
push: vi.fn(),
replace: vi.fn(),
prefetch: vi.fn(),
prefetchRoutes: vi.fn()
}));
// Mock localStorage
const localStorageMock = (() => {
let store = {};
return {
getItem: vi.fn((key) => store[key] || null),
setItem: vi.fn((key, value) => { store[key] = value; }),
removeItem: vi.fn((key) => { delete store[key]; }),
clear: () => { store = {}; },
get length() { return Object.keys(store).length; },
key: vi.fn((i) => Object.keys(store)[i] || null)
};
})();
Object.defineProperty(global, 'localStorage', { value: localStorageMock });
Object.defineProperty(global, 'sessionStorage', { value: localStorageMock });
// Mock console.log to reduce noise in tests
const originalLog = console.log;
console.log = vi.fn((...args) => {
// Keep activity store and task drawer logs for test output
const firstArg = args[0];
if (typeof firstArg === 'string' &&
(firstArg.includes('[activityStore]') ||
firstArg.includes('[taskDrawer]') ||
firstArg.includes('[SidebarStore]'))) {
originalLog.apply(console, args);
}
});
// [/DEF:setupTests:Module]

View File

@@ -0,0 +1,115 @@
// @RELATION: VERIFIES -> frontend/src/lib/stores/sidebar.js
// [DEF:frontend.src.lib.stores.__tests__.sidebar:Module]
// @TIER: STANDARD
// @PURPOSE: Unit tests for sidebar store
// @LAYER: Domain (Tests)
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { get } from 'svelte/store';
import { sidebarStore, toggleSidebar, setActiveItem, setMobileOpen, closeMobile, toggleMobileSidebar } from '../sidebar.js';
// Mock the $app/environment module
vi.mock('$app/environment', () => ({
browser: false
}));
describe('SidebarStore', () => {
// [DEF:test_sidebar_initial_state:Function]
// @TEST: Store initializes with default values
// @PRE: No localStorage state
// @POST: Default state is { isExpanded: true, activeCategory: 'dashboards', activeItem: '/dashboards', isMobileOpen: false }
describe('initial state', () => {
it('should have default values when no localStorage', () => {
const state = get(sidebarStore);
expect(state.isExpanded).toBe(true);
expect(state.activeCategory).toBe('dashboards');
expect(state.activeItem).toBe('/dashboards');
expect(state.isMobileOpen).toBe(false);
});
});
// [DEF:test_toggleSidebar:Function]
// @TEST: toggleSidebar toggles isExpanded state
// @PRE: Store is initialized
// @POST: isExpanded is toggled from previous value
describe('toggleSidebar', () => {
it('should toggle isExpanded from true to false', () => {
const initialState = get(sidebarStore);
expect(initialState.isExpanded).toBe(true);
toggleSidebar();
const newState = get(sidebarStore);
expect(newState.isExpanded).toBe(false);
});
it('should toggle isExpanded from false to true', () => {
toggleSidebar(); // Now false
toggleSidebar(); // Should be true again
const state = get(sidebarStore);
expect(state.isExpanded).toBe(true);
});
});
// [DEF:test_setActiveItem:Function]
// @TEST: setActiveItem updates activeCategory and activeItem
// @PRE: Store is initialized
// @POST: activeCategory and activeItem are updated
describe('setActiveItem', () => {
it('should update activeCategory and activeItem', () => {
setActiveItem('datasets', '/datasets');
const state = get(sidebarStore);
expect(state.activeCategory).toBe('datasets');
expect(state.activeItem).toBe('/datasets');
});
it('should update to admin category', () => {
setActiveItem('admin', '/settings');
const state = get(sidebarStore);
expect(state.activeCategory).toBe('admin');
expect(state.activeItem).toBe('/settings');
});
});
// [DEF:test_mobile_functions:Function]
// @TEST: Mobile functions correctly update isMobileOpen
// @PRE: Store is initialized
// @POST: isMobileOpen is correctly updated
describe('mobile functions', () => {
it('should set isMobileOpen to true with setMobileOpen', () => {
setMobileOpen(true);
const state = get(sidebarStore);
expect(state.isMobileOpen).toBe(true);
});
it('should set isMobileOpen to false with closeMobile', () => {
setMobileOpen(true);
closeMobile();
const state = get(sidebarStore);
expect(state.isMobileOpen).toBe(false);
});
it('should toggle isMobileOpen with toggleMobileSidebar', () => {
const initialState = get(sidebarStore);
const initialMobileOpen = initialState.isMobileOpen;
toggleMobileSidebar();
const state1 = get(sidebarStore);
expect(state1.isMobileOpen).toBe(!initialMobileOpen);
toggleMobileSidebar();
const state2 = get(sidebarStore);
expect(state2.isMobileOpen).toBe(initialMobileOpen);
});
});
});
// [/DEF:frontend.src.lib.stores.__tests__.sidebar:Module]

View File

@@ -0,0 +1,48 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { get } from 'svelte/store';
import { taskDrawerStore, openDrawerForTask, closeDrawer, updateResourceTask } from '../taskDrawer.js';
describe('taskDrawerStore', () => {
beforeEach(() => {
taskDrawerStore.set({
isOpen: false,
activeTaskId: null,
resourceTaskMap: {}
});
});
it('should open drawer for a specific task', () => {
openDrawerForTask('task-123');
const state = get(taskDrawerStore);
expect(state.isOpen).toBe(true);
expect(state.activeTaskId).toBe('task-123');
});
it('should close drawer and clear active task', () => {
openDrawerForTask('task-123');
closeDrawer();
const state = get(taskDrawerStore);
expect(state.isOpen).toBe(false);
expect(state.activeTaskId).toBe(null);
});
it('should update resource task mapping for running task', () => {
updateResourceTask('dash-1', 'task-1', 'RUNNING');
const state = get(taskDrawerStore);
expect(state.resourceTaskMap['dash-1']).toEqual({ taskId: 'task-1', status: 'RUNNING' });
});
it('should remove mapping when task completes (SUCCESS)', () => {
updateResourceTask('dash-1', 'task-1', 'RUNNING');
updateResourceTask('dash-1', 'task-1', 'SUCCESS');
const state = get(taskDrawerStore);
expect(state.resourceTaskMap['dash-1']).toBeUndefined();
});
it('should remove mapping when task fails (ERROR)', () => {
updateResourceTask('dash-1', 'task-1', 'RUNNING');
updateResourceTask('dash-1', 'task-1', 'ERROR');
const state = get(taskDrawerStore);
expect(state.resourceTaskMap['dash-1']).toBeUndefined();
});
});

View File

@@ -0,0 +1,119 @@
// [DEF:frontend.src.lib.stores.__tests__.test_activity:Module]
// @TIER: STANDARD
// @PURPOSE: Unit tests for activity store
// @LAYER: UI
// @RELATION: VERIFIES -> frontend.src.lib.stores.activity
// @RELATION: DEPENDS_ON -> frontend.src.lib.stores.taskDrawer
import { describe, it, expect, beforeEach, vi } from 'vitest';
describe('activity store', () => {
beforeEach(async () => {
vi.resetModules();
});
it('should have zero active count initially', async () => {
const { activityStore } = await import('../activity.js');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.activeCount).toBe(0);
expect(state.recentTasks).toEqual([]);
});
it('should count RUNNING tasks as active', async () => {
const { taskDrawerStore, updateResourceTask } = await import('../taskDrawer.js');
const { activityStore } = await import('../activity.js');
// Add a running task
updateResourceTask('dashboard-1', 'task-1', 'RUNNING');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.activeCount).toBe(1);
});
it('should not count SUCCESS tasks as active', async () => {
const { updateResourceTask } = await import('../taskDrawer.js');
const { activityStore } = await import('../activity.js');
// Add a success task
updateResourceTask('dashboard-1', 'task-1', 'SUCCESS');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.activeCount).toBe(0);
});
it('should not count ERROR tasks as active', async () => {
const { updateResourceTask } = await import('../taskDrawer.js');
const { activityStore } = await import('../activity.js');
// Add an error task
updateResourceTask('dashboard-1', 'task-1', 'ERROR');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.activeCount).toBe(0);
});
it('should not count WAITING_INPUT as active', async () => {
const { updateResourceTask } = await import('../taskDrawer.js');
const { activityStore } = await import('../activity.js');
// Add a waiting input task - should NOT be counted as active per contract
// Only RUNNING tasks count as active
updateResourceTask('dashboard-1', 'task-1', 'WAITING_INPUT');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.activeCount).toBe(0);
});
it('should track multiple running tasks', async () => {
const { updateResourceTask } = await import('../taskDrawer.js');
const { activityStore } = await import('../activity.js');
// Add multiple running tasks
updateResourceTask('dashboard-1', 'task-1', 'RUNNING');
updateResourceTask('dashboard-2', 'task-2', 'RUNNING');
updateResourceTask('dataset-1', 'task-3', 'RUNNING');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.activeCount).toBe(3);
});
it('should return recent tasks', async () => {
const { updateResourceTask } = await import('../taskDrawer.js');
const { activityStore } = await import('../activity.js');
// Add multiple tasks
updateResourceTask('dashboard-1', 'task-1', 'RUNNING');
updateResourceTask('dataset-1', 'task-2', 'SUCCESS');
updateResourceTask('storage-1', 'task-3', 'ERROR');
let state = null;
const unsubscribe = activityStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.recentTasks.length).toBeGreaterThan(0);
expect(state.recentTasks[0]).toHaveProperty('taskId');
expect(state.recentTasks[0]).toHaveProperty('resourceId');
expect(state.recentTasks[0]).toHaveProperty('status');
});
});
// [/DEF:frontend.src.lib.stores.__tests__.test_activity:Module]

View File

@@ -0,0 +1,142 @@
// [DEF:frontend.src.lib.stores.__tests__.test_sidebar:Module]
// @TIER: STANDARD
// @PURPOSE: Unit tests for sidebar store
// @LAYER: UI
// @RELATION: VERIFIES -> frontend.src.lib.stores.sidebar
import { describe, it, expect, beforeEach, vi } from 'vitest';
// Mock browser environment
vi.mock('$app/environment', () => ({
browser: true
}));
// Mock localStorage
const localStorageMock = (() => {
let store = {};
return {
getItem: vi.fn((key) => store[key] || null),
setItem: vi.fn((key, value) => { store[key] = value; }),
clear: () => { store = {}; }
};
})();
Object.defineProperty(global, 'localStorage', { value: localStorageMock });
describe('sidebar store', () => {
// Reset modules to get fresh store
beforeEach(async () => {
localStorageMock.clear();
vi.clearAllMocks();
vi.resetModules();
});
it('should have correct initial state', async () => {
const { sidebarStore } = await import('../sidebar.js');
let state = null;
const unsubscribe = sidebarStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.isExpanded).toBe(true);
expect(state.activeCategory).toBe('dashboards');
expect(state.activeItem).toBe('/dashboards');
expect(state.isMobileOpen).toBe(false);
});
it('should toggle sidebar expansion', async () => {
const { sidebarStore, toggleSidebar } = await import('../sidebar.js');
let state = null;
const unsub1 = sidebarStore.subscribe(s => { state = s; });
unsub1();
expect(state.isExpanded).toBe(true);
toggleSidebar();
const unsub2 = sidebarStore.subscribe(s => { state = s; });
unsub2();
expect(state.isExpanded).toBe(false);
expect(localStorageMock.setItem).toHaveBeenCalled();
});
it('should set active category and item', async () => {
const { sidebarStore, setActiveItem } = await import('../sidebar.js');
setActiveItem('datasets', '/datasets');
let state = null;
const unsub = sidebarStore.subscribe(s => { state = s; });
unsub();
expect(state.activeCategory).toBe('datasets');
expect(state.activeItem).toBe('/datasets');
expect(localStorageMock.setItem).toHaveBeenCalled();
});
it('should set mobile open state', async () => {
const { sidebarStore, setMobileOpen } = await import('../sidebar.js');
setMobileOpen(true);
let state = null;
const unsub = sidebarStore.subscribe(s => { state = s; });
unsub();
expect(state.isMobileOpen).toBe(true);
});
it('should close mobile sidebar', async () => {
const { sidebarStore, closeMobile } = await import('../sidebar.js');
// First open mobile
let state = null;
sidebarStore.update(s => ({ ...s, isMobileOpen: true }));
const unsub1 = sidebarStore.subscribe(s => { state = s; });
unsub1();
expect(state.isMobileOpen).toBe(true);
closeMobile();
const unsub2 = sidebarStore.subscribe(s => { state = s; });
unsub2();
expect(state.isMobileOpen).toBe(false);
});
it('should toggle mobile sidebar', async () => {
const { sidebarStore, toggleMobileSidebar } = await import('../sidebar.js');
toggleMobileSidebar();
let state = null;
const unsub1 = sidebarStore.subscribe(s => { state = s; });
unsub1();
expect(state.isMobileOpen).toBe(true);
toggleMobileSidebar();
const unsub2 = sidebarStore.subscribe(s => { state = s; });
unsub2();
expect(state.isMobileOpen).toBe(false);
});
it('should load state from localStorage', async () => {
localStorageMock.getItem.mockReturnValue(JSON.stringify({
isExpanded: false,
activeCategory: 'storage',
activeItem: '/storage',
isMobileOpen: true
}));
// Re-import with localStorage populated
vi.resetModules();
const { sidebarStore } = await import('../sidebar.js');
let state = null;
const unsub = sidebarStore.subscribe(s => { state = s; });
unsub();
expect(state.isExpanded).toBe(false);
expect(state.activeCategory).toBe('storage');
expect(state.isMobileOpen).toBe(true);
});
});
// [/DEF:frontend.src.lib.stores.__tests__.test_sidebar:Module]

View File

@@ -0,0 +1,158 @@
// [DEF:frontend.src.lib.stores.__tests__.test_taskDrawer:Module]
// @TIER: CRITICAL
// @PURPOSE: Unit tests for task drawer store
// @LAYER: UI
// @RELATION: VERIFIES -> frontend.src.lib.stores.taskDrawer
import { describe, it, expect, beforeEach, vi } from 'vitest';
describe('taskDrawer store', () => {
beforeEach(async () => {
vi.resetModules();
});
it('should have correct initial state', async () => {
const { taskDrawerStore } = await import('../taskDrawer.js');
let state = null;
const unsubscribe = taskDrawerStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.isOpen).toBe(false);
expect(state.activeTaskId).toBeNull();
expect(state.resourceTaskMap).toEqual({});
});
it('should open drawer for specific task', async () => {
const { taskDrawerStore, openDrawerForTask } = await import('../taskDrawer.js');
openDrawerForTask('task-123');
let state = null;
const unsubscribe = taskDrawerStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.isOpen).toBe(true);
expect(state.activeTaskId).toBe('task-123');
});
it('should open drawer in list mode', async () => {
const { taskDrawerStore, openDrawer } = await import('../taskDrawer.js');
openDrawer();
let state = null;
const unsubscribe = taskDrawerStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.isOpen).toBe(true);
expect(state.activeTaskId).toBeNull();
});
it('should close drawer', async () => {
const { taskDrawerStore, openDrawerForTask, closeDrawer } = await import('../taskDrawer.js');
// First open drawer
openDrawerForTask('task-123');
let state = null;
const unsub1 = taskDrawerStore.subscribe(s => { state = s; });
unsub1();
expect(state.isOpen).toBe(true);
closeDrawer();
const unsub2 = taskDrawerStore.subscribe(s => { state = s; });
unsub2();
expect(state.isOpen).toBe(false);
expect(state.activeTaskId).toBeNull();
});
it('should update resource-task mapping', async () => {
const { taskDrawerStore, updateResourceTask } = await import('../taskDrawer.js');
updateResourceTask('dashboard-1', 'task-123', 'RUNNING');
let state = null;
const unsubscribe = taskDrawerStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.resourceTaskMap['dashboard-1']).toEqual({
taskId: 'task-123',
status: 'RUNNING'
});
});
it('should remove mapping on task completion (SUCCESS)', async () => {
const { taskDrawerStore, updateResourceTask } = await import('../taskDrawer.js');
// First add a running task
updateResourceTask('dashboard-1', 'task-123', 'RUNNING');
let state = null;
const unsub1 = taskDrawerStore.subscribe(s => { state = s; });
unsub1();
expect(state.resourceTaskMap['dashboard-1']).toBeDefined();
// Complete the task
updateResourceTask('dashboard-1', 'task-123', 'SUCCESS');
const unsub2 = taskDrawerStore.subscribe(s => { state = s; });
unsub2();
expect(state.resourceTaskMap['dashboard-1']).toBeUndefined();
});
it('should remove mapping on task error', async () => {
const { taskDrawerStore, updateResourceTask } = await import('../taskDrawer.js');
updateResourceTask('dataset-1', 'task-456', 'RUNNING');
let state = null;
const unsub1 = taskDrawerStore.subscribe(s => { state = s; });
unsub1();
expect(state.resourceTaskMap['dataset-1']).toBeDefined();
// Error the task
updateResourceTask('dataset-1', 'task-456', 'ERROR');
const unsub2 = taskDrawerStore.subscribe(s => { state = s; });
unsub2();
expect(state.resourceTaskMap['dataset-1']).toBeUndefined();
});
it('should keep mapping for WAITING_INPUT status', async () => {
const { taskDrawerStore, updateResourceTask } = await import('../taskDrawer.js');
updateResourceTask('dashboard-1', 'task-789', 'WAITING_INPUT');
let state = null;
const unsubscribe = taskDrawerStore.subscribe(s => { state = s; });
unsubscribe();
expect(state.resourceTaskMap['dashboard-1']).toEqual({
taskId: 'task-789',
status: 'WAITING_INPUT'
});
});
it('should get task for resource', async () => {
const { updateResourceTask, getTaskForResource } = await import('../taskDrawer.js');
updateResourceTask('dashboard-1', 'task-123', 'RUNNING');
const taskInfo = getTaskForResource('dashboard-1');
expect(taskInfo).toEqual({
taskId: 'task-123',
status: 'RUNNING'
});
});
it('should return null for resource without task', async () => {
const { getTaskForResource } = await import('../taskDrawer.js');
const taskInfo = getTaskForResource('non-existent');
expect(taskInfo).toBeNull();
});
});
// [/DEF:frontend.src.lib.stores.__tests__.test_taskDrawer:Module]