test: improve coverage across frontend and backend
Frontend (22 → 37 tests): - canvasStore: add tests for onNodesChange, onEdgesChange, onConnect, addNode with parentId, updateEdge, deleteEdge, setProxmoxContainerMode ON/OFF, loadCanvas parent-before-child ordering - edgeColors: 6 tests for EDGE_DEFAULT_COLORS (all types, hex format, values) Backend (40 → 80 tests): - test_canvas (new): load empty canvas, default viewport, save creates/updates/ deletes nodes+edges, viewport upsert, custom_colors, edge custom_color+path_style, auth guard - test_fingerprint (new): match_port (known, unknown, wrong protocol, banner match, banner no-match, no banner), fingerprint_ports (matched, unknown, mixed, empty, default protocol), suggest_node_type (proxmox, server, generic, priority) - test_nodes: update/delete 404, custom_colors CRUD, container_mode CRUD, auth guard - test_edges: update edge, update/delete 404, custom_color, path_style, auth guard
This commit is contained in:
@@ -93,4 +93,91 @@ describe('canvasStore', () => {
|
||||
useCanvasStore.getState().setSelectedNode(null)
|
||||
expect(useCanvasStore.getState().selectedNodeId).toBeNull()
|
||||
})
|
||||
|
||||
it('onNodesChange marks unsaved', () => {
|
||||
useCanvasStore.getState().addNode(makeNode('n1'))
|
||||
useCanvasStore.getState().markSaved()
|
||||
useCanvasStore.getState().onNodesChange([{ type: 'select', id: 'n1', selected: true }])
|
||||
expect(useCanvasStore.getState().hasUnsavedChanges).toBe(true)
|
||||
})
|
||||
|
||||
it('onEdgesChange marks unsaved', () => {
|
||||
useCanvasStore.setState((s) => ({ edges: [...s.edges, makeEdge('e1', 'n1', 'n2')] }))
|
||||
useCanvasStore.getState().markSaved()
|
||||
useCanvasStore.getState().onEdgesChange([{ type: 'select', id: 'e1', selected: true }])
|
||||
expect(useCanvasStore.getState().hasUnsavedChanges).toBe(true)
|
||||
})
|
||||
|
||||
it('onConnect adds an edge between two nodes', () => {
|
||||
useCanvasStore.getState().onConnect({ source: 'n1', target: 'n2', sourceHandle: null, targetHandle: null })
|
||||
const { edges, hasUnsavedChanges } = useCanvasStore.getState()
|
||||
expect(edges).toHaveLength(1)
|
||||
expect(edges[0].source).toBe('n1')
|
||||
expect(edges[0].target).toBe('n2')
|
||||
expect(hasUnsavedChanges).toBe(true)
|
||||
})
|
||||
|
||||
it('addNode with parent_id sets parentId and extent', () => {
|
||||
useCanvasStore.getState().addNode(makeNode('parent'))
|
||||
useCanvasStore.getState().addNode(makeNode('child', { parent_id: 'parent' }))
|
||||
const child = useCanvasStore.getState().nodes.find((n) => n.id === 'child')
|
||||
expect(child?.parentId).toBe('parent')
|
||||
expect(child?.extent).toBe('parent')
|
||||
})
|
||||
|
||||
it('updateEdge updates edge data and marks unsaved', () => {
|
||||
useCanvasStore.setState((s) => ({ edges: [...s.edges, makeEdge('e1', 'n1', 'n2')] }))
|
||||
useCanvasStore.getState().markSaved()
|
||||
useCanvasStore.getState().updateEdge('e1', { type: 'wifi', label: 'uplink' })
|
||||
const edge = useCanvasStore.getState().edges.find((e) => e.id === 'e1')
|
||||
expect(edge?.data?.type).toBe('wifi')
|
||||
expect(edge?.data?.label).toBe('uplink')
|
||||
expect(useCanvasStore.getState().hasUnsavedChanges).toBe(true)
|
||||
})
|
||||
|
||||
it('deleteEdge removes the edge and marks unsaved', () => {
|
||||
useCanvasStore.setState((s) => ({ edges: [...s.edges, makeEdge('e1', 'n1', 'n2'), makeEdge('e2', 'n2', 'n3')] }))
|
||||
useCanvasStore.getState().markSaved()
|
||||
useCanvasStore.getState().deleteEdge('e1')
|
||||
const { edges, hasUnsavedChanges } = useCanvasStore.getState()
|
||||
expect(edges.find((e) => e.id === 'e1')).toBeUndefined()
|
||||
expect(edges.find((e) => e.id === 'e2')).toBeDefined()
|
||||
expect(hasUnsavedChanges).toBe(true)
|
||||
})
|
||||
|
||||
it('setProxmoxContainerMode ON nests children inside proxmox', () => {
|
||||
const proxmox: Node<NodeData> = { id: 'px', type: 'proxmox', position: { x: 0, y: 0 }, data: { label: 'px', type: 'proxmox', status: 'unknown', services: [], container_mode: false } }
|
||||
const child = makeNode('vm1', { parent_id: 'px', type: 'vm' })
|
||||
useCanvasStore.setState({ nodes: [proxmox, child] })
|
||||
useCanvasStore.getState().setProxmoxContainerMode('px', true)
|
||||
const { nodes } = useCanvasStore.getState()
|
||||
const updatedProxy = nodes.find((n) => n.id === 'px')
|
||||
const updatedChild = nodes.find((n) => n.id === 'vm1')
|
||||
expect(updatedProxy?.data.container_mode).toBe(true)
|
||||
expect(updatedProxy?.width).toBe(300)
|
||||
expect(updatedChild?.parentId).toBe('px')
|
||||
expect(updatedChild?.extent).toBe('parent')
|
||||
})
|
||||
|
||||
it('setProxmoxContainerMode OFF detaches children', () => {
|
||||
const proxmox: Node<NodeData> = { id: 'px', type: 'proxmox', position: { x: 0, y: 0 }, data: { label: 'px', type: 'proxmox', status: 'unknown', services: [], container_mode: true }, parentId: undefined }
|
||||
const child: Node<NodeData> = { id: 'vm1', type: 'vm', position: { x: 0, y: 0 }, data: { label: 'vm1', type: 'vm', status: 'unknown', services: [], parent_id: 'px' }, parentId: 'px', extent: 'parent' }
|
||||
useCanvasStore.setState({ nodes: [proxmox, child] })
|
||||
useCanvasStore.getState().setProxmoxContainerMode('px', false)
|
||||
const { nodes } = useCanvasStore.getState()
|
||||
const updatedChild = nodes.find((n) => n.id === 'vm1')
|
||||
expect(nodes.find((n) => n.id === 'px')?.data.container_mode).toBe(false)
|
||||
expect(updatedChild?.parentId).toBeUndefined()
|
||||
expect(updatedChild?.extent).toBeUndefined()
|
||||
})
|
||||
|
||||
it('loadCanvas sorts parents before children', () => {
|
||||
const parent = makeNode('p1')
|
||||
const child: Node<NodeData> = { ...makeNode('c1', { parent_id: 'p1' }), parentId: 'p1', extent: 'parent' }
|
||||
useCanvasStore.getState().loadCanvas([child, parent], [])
|
||||
const { nodes } = useCanvasStore.getState()
|
||||
const parentIdx = nodes.findIndex((n) => n.id === 'p1')
|
||||
const childIdx = nodes.findIndex((n) => n.id === 'c1')
|
||||
expect(parentIdx).toBeLessThan(childIdx)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { EDGE_DEFAULT_COLORS } from '../edgeColors'
|
||||
import type { EdgeType } from '@/types'
|
||||
|
||||
const EDGE_TYPES: EdgeType[] = ['ethernet', 'wifi', 'iot', 'vlan', 'virtual']
|
||||
|
||||
describe('EDGE_DEFAULT_COLORS', () => {
|
||||
it('has an entry for every EdgeType', () => {
|
||||
for (const type of EDGE_TYPES) {
|
||||
expect(EDGE_DEFAULT_COLORS[type]).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('all colors are valid hex strings', () => {
|
||||
for (const color of Object.values(EDGE_DEFAULT_COLORS)) {
|
||||
expect(color).toMatch(/^#[0-9a-fA-F]{6}$/)
|
||||
}
|
||||
})
|
||||
|
||||
it('ethernet default is the dark gray neutral color', () => {
|
||||
expect(EDGE_DEFAULT_COLORS.ethernet).toBe('#30363d')
|
||||
})
|
||||
|
||||
it('wifi default is cyan', () => {
|
||||
expect(EDGE_DEFAULT_COLORS.wifi).toBe('#00d4ff')
|
||||
})
|
||||
|
||||
it('iot default is amber', () => {
|
||||
expect(EDGE_DEFAULT_COLORS.iot).toBe('#e3b341')
|
||||
})
|
||||
|
||||
it('virtual default is muted gray', () => {
|
||||
expect(EDGE_DEFAULT_COLORS.virtual).toBe('#8b949e')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user