feat: integrate all 10 skills into homelab-topology
- Added api-security-hardening (helmet, rate limits) - Added nodejs-backend-patterns (error handling) - Added observability-monitoring (pino logging) - Added websocket-engineer (socket.io real-time updates) - Added docker (Multi-stage build, compose) - Added vitest (testing configuration and store tests) - Added data-visualizer (MetricsBar and HostChart) - Added infrastructure-monitoring/proxmox-admin/network-engineer types - Fixed UI accessibility and styling - Cleaned up node_modules tracking
This commit is contained in:
146
src/store/topologyStore.test.ts
Normal file
146
src/store/topologyStore.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, test, expect, afterEach } from 'vitest';
|
||||
import { useTopologyStore } from './topologyStore';
|
||||
import { TopologyNode } from '../types';
|
||||
|
||||
const mockNodes: TopologyNode[] = [
|
||||
{
|
||||
id: 'gateway-1',
|
||||
name: 'Gateway',
|
||||
type: 'gateway',
|
||||
data: {
|
||||
status: 'running',
|
||||
ip: '10.0.0.1',
|
||||
parentId: undefined,
|
||||
importance: 5,
|
||||
metadata: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'host-1',
|
||||
name: 'Ubuntu',
|
||||
type: 'host_vm',
|
||||
data: {
|
||||
status: 'running',
|
||||
ip: '10.0.0.10',
|
||||
parentId: 'gateway-1',
|
||||
importance: 4,
|
||||
metadata: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'service-1',
|
||||
name: 'Traefik',
|
||||
type: 'service',
|
||||
data: {
|
||||
status: 'running',
|
||||
ip: '10.0.0.10',
|
||||
parentId: 'host-1',
|
||||
category: 'infra',
|
||||
importance: 5,
|
||||
metadata: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'service-2',
|
||||
name: 'Plex',
|
||||
type: 'service',
|
||||
data: {
|
||||
status: 'stopped',
|
||||
ip: '10.0.0.10',
|
||||
parentId: 'host-1',
|
||||
category: 'media',
|
||||
importance: 2,
|
||||
metadata: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('topologyStore', () => {
|
||||
afterEach(() => {
|
||||
// Reset store state between tests
|
||||
useTopologyStore.setState({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
selectedNodeId: null,
|
||||
searchQuery: '',
|
||||
typeFilters: [],
|
||||
statusFilter: 'all',
|
||||
highlightPath: [],
|
||||
});
|
||||
});
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Basic state management
|
||||
* ------------------------------------------------------------- */
|
||||
|
||||
describe('setNodes', () => {
|
||||
test('sets nodes correctly', () => {
|
||||
useTopologyStore.getState().setNodes(mockNodes);
|
||||
expect(useTopologyStore.getState().nodes).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSelectedNode', () => {
|
||||
test('selects a node by id', () => {
|
||||
useTopologyStore.getState().setNodes(mockNodes);
|
||||
useTopologyStore.getState().setSelectedNode('host-1');
|
||||
expect(useTopologyStore.getState().selectedNodeId).toBe('host-1');
|
||||
});
|
||||
|
||||
test('clears selection with null', () => {
|
||||
useTopologyStore.getState().setSelectedNode('host-1');
|
||||
useTopologyStore.getState().setSelectedNode(null);
|
||||
expect(useTopologyStore.getState().selectedNodeId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Filtering
|
||||
* ------------------------------------------------------------- */
|
||||
|
||||
describe('getFilteredNodes', () => {
|
||||
test('returns all nodes when no filters active', () => {
|
||||
useTopologyStore.getState().setNodes(mockNodes);
|
||||
const filtered = useTopologyStore.getState().getFilteredNodes();
|
||||
expect(filtered).toHaveLength(4);
|
||||
});
|
||||
|
||||
test('filters by search query', () => {
|
||||
useTopologyStore.getState().setNodes(mockNodes);
|
||||
useTopologyStore.getState().setSearchQuery('traefik');
|
||||
const filtered = useTopologyStore.getState().getFilteredNodes();
|
||||
expect(filtered.some(n => n.name === 'Traefik')).toBe(true);
|
||||
});
|
||||
|
||||
test('filters by status', () => {
|
||||
useTopologyStore.getState().setNodes(mockNodes);
|
||||
useTopologyStore.getState().setStatusFilter('stopped');
|
||||
const filtered = useTopologyStore.getState().getFilteredNodes();
|
||||
expect(filtered.every(n => n.data.status === 'stopped')).toBe(true);
|
||||
});
|
||||
|
||||
test('filters by type', () => {
|
||||
useTopologyStore.getState().setNodes(mockNodes);
|
||||
useTopologyStore.getState().toggleTypeFilter('service');
|
||||
const filtered = useTopologyStore.getState().getFilteredNodes();
|
||||
expect(filtered.every(n => n.type === 'service')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Type filter toggle
|
||||
* ------------------------------------------------------------- */
|
||||
|
||||
describe('toggleTypeFilter', () => {
|
||||
test('adds type when not present', () => {
|
||||
useTopologyStore.getState().toggleTypeFilter('gateway');
|
||||
expect(useTopologyStore.getState().typeFilters).toContain('gateway');
|
||||
});
|
||||
|
||||
test('removes type when already present', () => {
|
||||
useTopologyStore.getState().toggleTypeFilter('gateway');
|
||||
useTopologyStore.getState().toggleTypeFilter('gateway');
|
||||
expect(useTopologyStore.getState().typeFilters).not.toContain('gateway');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user