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:
2026-02-20 20:35:08 -08:00
parent 3dc5d236a2
commit 6dd679b8e0
14455 changed files with 3862 additions and 2194786 deletions

View 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');
});
});
});