Files
tyto/backend/internal/collectors/network_test.go
vikingowl f4dbc55851 feat: add dashboard customization, alerts, PWA, and mobile support
Dashboard Editor & Layout:
- Full-screen visual editor for reorganizing cards
- Drag-and-drop cards between sections
- Toggle card visibility with persistence to localStorage
- Reset to default layout option

Alerts System:
- Threshold-based alerts for CPU, memory, temperature, disk, GPU
- Alert manager with duration requirements
- AlertsCard component with settings UI
- API endpoints for alerts CRUD

New Collectors:
- Docker container monitoring with parallel stats fetching
- Systemd service status via D-Bus
- Historical metrics storage (1 hour at 1s intervals)

PWA Support:
- Service worker with offline caching
- Web app manifest with SVG icons
- iOS PWA meta tags

Mobile Responsive:
- Collapsible hamburger menu on mobile
- Adaptive grid layouts for all screen sizes
- Touch-friendly hover states
- Safe area insets for notched devices

UI Enhancements:
- Light/dark theme toggle with persistence
- Keyboard shortcuts (T=theme, R=refresh, ?=help)
- Per-process expandable details in ProcessesCard
- Sparkline charts for historical data

Performance Fixes:
- Buffered SSE channels to prevent blocking
- Parallel Docker stats collection with timeout
- D-Bus timeout for systemd collector

Tests:
- Unit tests for CPU, memory, network collectors
- Alert manager tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 05:35:28 +01:00

128 lines
4.3 KiB
Go

package collectors
import (
"os"
"path/filepath"
"testing"
)
func TestNetworkCollector(t *testing.T) {
tmpDir := t.TempDir()
// Create mock net directory
netDir := filepath.Join(tmpDir, "net")
if err := os.MkdirAll(netDir, 0755); err != nil {
t.Fatal(err)
}
// Create mock /proc/net/dev
devContent := `Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo: 12345678 123456 0 0 0 0 0 0 12345678 123456 0 0 0 0 0 0
eth0: 987654321 9876543 0 0 0 0 0 0 123456789 1234567 0 0 0 0 0 0
docker0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
`
if err := os.WriteFile(filepath.Join(netDir, "dev"), []byte(devContent), 0644); err != nil {
t.Fatal(err)
}
// Create mock /proc/net/tcp (for connection count)
tcpContent := ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12345 1 0000000000000000 100 0 0 10 0
1: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 33 0 23456 1 0000000000000000 100 0 0 10 0
`
if err := os.WriteFile(filepath.Join(netDir, "tcp"), []byte(tcpContent), 0644); err != nil {
t.Fatal(err)
}
collector := NewNetworkCollector(tmpDir)
stats, err := collector.Collect()
if err != nil {
t.Fatalf("Collect failed: %v", err)
}
// Should have eth0 (excluding lo and docker0 virtual interfaces might depend on impl)
if len(stats.Interfaces) == 0 {
t.Error("Expected at least one interface")
}
// Find eth0
var eth0Found bool
for _, iface := range stats.Interfaces {
if iface.Name == "eth0" {
eth0Found = true
if iface.RxBytes != 987654321 {
t.Errorf("Expected eth0 RxBytes=987654321, got %d", iface.RxBytes)
}
if iface.TxBytes != 123456789 {
t.Errorf("Expected eth0 TxBytes=123456789, got %d", iface.TxBytes)
}
if iface.RxPackets != 9876543 {
t.Errorf("Expected eth0 RxPackets=9876543, got %d", iface.RxPackets)
}
if iface.TxPackets != 1234567 {
t.Errorf("Expected eth0 TxPackets=1234567, got %d", iface.TxPackets)
}
}
}
if !eth0Found {
t.Error("eth0 interface not found")
}
// Check connection count (2 connections in mock tcp file, minus header)
if stats.ConnectionCount != 2 {
t.Errorf("Expected ConnectionCount=2, got %d", stats.ConnectionCount)
}
}
func TestNetworkCollector_MissingFiles(t *testing.T) {
tmpDir := t.TempDir()
collector := NewNetworkCollector(tmpDir)
stats, err := collector.Collect()
// Should not panic
if err != nil {
return // Error is acceptable
}
if len(stats.Interfaces) != 0 {
t.Errorf("Expected no interfaces with missing files, got %d", len(stats.Interfaces))
}
}
func TestNetworkCollector_ParsesVirtualInterfaces(t *testing.T) {
tmpDir := t.TempDir()
netDir := filepath.Join(tmpDir, "net")
if err := os.MkdirAll(netDir, 0755); err != nil {
t.Fatal(err)
}
// Only virtual interfaces
devContent := `Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo: 12345 1234 0 0 0 0 0 0 12345 1234 0 0 0 0 0 0
veth123: 5000 50 0 0 0 0 0 0 6000 60 0 0 0 0 0 0
br-abc: 7000 70 0 0 0 0 0 0 8000 80 0 0 0 0 0 0
`
if err := os.WriteFile(filepath.Join(netDir, "dev"), []byte(devContent), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(netDir, "tcp"), []byte(""), 0644); err != nil {
t.Fatal(err)
}
collector := NewNetworkCollector(tmpDir)
stats, err := collector.Collect()
if err != nil {
t.Fatalf("Collect failed: %v", err)
}
// Should parse all interfaces (implementation may or may not filter virtual)
// At minimum, should not crash and should handle the format correctly
if stats.Interfaces == nil {
t.Error("Expected non-nil Interfaces")
}
}