Files
wild-central/internal/sse/watchers_test.go
Paul Payne beb643f76f feat: Extract Wild Central as standalone Go service
Extract the Central networking functionality from wild-cloud/api into
a standalone service. Wild Central manages DNS (dnsmasq), gateway
(HAProxy), firewall (nftables), VPN (WireGuard), TLS (certbot),
security (CrowdSec), and DDNS — all the network appliance concerns.

All Cloud-specific code (instances, clusters, nodes, apps, backups,
operations, kubectl/talosctl tooling) has been removed. The API struct
and route registration contain only Central endpoints. Tests updated
to match the new API signature.

Builds and all tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-08 23:31:16 +00:00

435 lines
11 KiB
Go

package sse
import (
"fmt"
"testing"
"time"
)
func TestKubectlWatcherJSONParsing(t *testing.T) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
// Test valid pod JSON parsing
validPodJSON := `{
"type": "ADDED",
"object": {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test-pod",
"namespace": "default",
"labels": {
"app": "test-app"
}
},
"status": {
"phase": "Running",
"conditions": []
}
}
}`
// Register a client to receive events
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Parse the JSON
watcher.parsePodEvent([]byte(validPodJSON), "test-instance")
// Should receive parsed event
select {
case event := <-client.Channel:
if event.Type != "pod:added" {
t.Errorf("Expected event type 'pod:added', got '%s'", event.Type)
}
if podData, ok := event.Data.(map[string]any); ok {
if podData["name"] != "test-pod" {
t.Errorf("Expected pod name 'test-pod', got '%v'", podData["name"])
}
if podData["namespace"] != "default" {
t.Errorf("Expected namespace 'default', got '%v'", podData["namespace"])
}
} else {
t.Error("Event data is not a map")
}
case <-time.After(100 * time.Millisecond):
t.Error("Did not receive pod event")
}
}
func TestKubectlWatcherEventTypeMapping(t *testing.T) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
testCases := []struct {
watchType string
expectedType string
}{
{"ADDED", "pod:added"},
{"MODIFIED", "pod:modified"},
{"DELETED", "pod:deleted"},
}
for _, tc := range testCases {
t.Run(tc.watchType, func(t *testing.T) {
// Register a client
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
podJSON := fmt.Sprintf(`{
"type": "%s",
"object": {
"kind": "Pod",
"metadata": {
"name": "test-pod",
"namespace": "default"
}
}
}`, tc.watchType)
// Parse the event
watcher.parsePodEvent([]byte(podJSON), "test-instance")
// Check received event type
select {
case event := <-client.Channel:
if event.Type != tc.expectedType {
t.Errorf("Expected event type '%s', got '%s'", tc.expectedType, event.Type)
}
case <-time.After(100 * time.Millisecond):
t.Error("Did not receive event")
}
})
}
}
func TestDeploymentEventParsing(t *testing.T) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
deploymentJSON := `{
"type": "MODIFIED",
"object": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "test-deployment",
"namespace": "default"
},
"spec": {
"replicas": 3
},
"status": {
"replicas": 3,
"readyReplicas": 2,
"availableReplicas": 2
}
}
}`
// Register a client
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Parse deployment event
watcher.parseDeploymentEvent([]byte(deploymentJSON), "test-instance")
// Check received event
select {
case event := <-client.Channel:
if event.Type != "deployment:modified" {
t.Errorf("Expected event type 'deployment:modified', got '%s'", event.Type)
}
if deployData, ok := event.Data.(map[string]any); ok {
if deployData["name"] != "test-deployment" {
t.Errorf("Expected deployment name 'test-deployment', got '%v'", deployData["name"])
}
if deployData["readyReplicas"] != int32(2) {
t.Errorf("Expected 2 ready replicas, got '%v'", deployData["readyReplicas"])
}
}
case <-time.After(100 * time.Millisecond):
t.Error("Did not receive deployment event")
}
}
func TestServiceEventParsing(t *testing.T) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
serviceJSON := `{
"type": "ADDED",
"object": {
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "test-service",
"namespace": "default"
},
"spec": {
"type": "LoadBalancer",
"clusterIP": "10.96.0.1",
"ports": [
{
"port": 80,
"targetPort": 8080,
"protocol": "TCP"
}
]
},
"status": {
"loadBalancer": {
"ingress": [
{
"ip": "192.168.1.100"
}
]
}
}
}
}`
// Register a client
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Parse service event
watcher.parseServiceEvent([]byte(serviceJSON), "test-instance")
// Check received event
select {
case event := <-client.Channel:
if event.Type != "service:added" {
t.Errorf("Expected event type 'service:added', got '%s'", event.Type)
}
if serviceData, ok := event.Data.(map[string]any); ok {
if serviceData["name"] != "test-service" {
t.Errorf("Expected service name 'test-service', got '%v'", serviceData["name"])
}
if serviceData["type"] != "LoadBalancer" {
t.Errorf("Expected service type 'LoadBalancer', got '%v'", serviceData["type"])
}
}
case <-time.After(100 * time.Millisecond):
t.Error("Did not receive service event")
}
}
// TestTalosEventParsing is commented out as TalosWatcher doesn't expose parsing methods
// The actual parsing happens internally in the watchEvents method
/*
func TestTalosEventParsing(t *testing.T) {
manager := NewManager()
watcher := &TalosWatcher{
instanceName: "test-instance",
talosconfig: "/tmp/test-talosconfig",
nodeIP: "192.168.1.10",
sseManager: manager,
}
// Test machine config event
machineConfigEvent := `seq:task message:update successful node:192.168.1.10 type:MachineConfig`
// Register a client
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Parse talos event
watcher.parseTalosEventLine(machineConfigEvent)
// Check received event
select {
case event := <-client.Channel:
if event.Type != "talos.MachineConfig" {
t.Errorf("Expected event type 'talos.MachineConfig', got '%s'", event.Type)
}
if talosData, ok := event.Data.(map[string]any); ok {
if talosData["message"] != "update successful" {
t.Errorf("Expected message 'update successful', got '%v'", talosData["message"])
}
if talosData["node"] != "192.168.1.10" {
t.Errorf("Expected node '192.168.1.10', got '%v'", talosData["node"])
}
}
case <-time.After(100 * time.Millisecond):
t.Error("Did not receive talos event")
}
}
*/
func TestWatcherManagerLifecycle(t *testing.T) {
sseManager := NewManager()
watcherManager := NewWatcherManager(sseManager)
// Test starting watchers for an instance
err := watcherManager.StartWatchers("test-instance", "/tmp/kubeconfig", "/tmp/talosconfig", "192.168.1.10")
if err != nil {
// This will likely fail because the configs don't exist, but we can test the manager state
t.Logf("Expected error starting watchers with non-existent configs: %v", err)
}
// Check if instance is registered (even if watchers failed to start)
watcherManager.mu.RLock()
_, exists := watcherManager.watchers["test-instance"]
watcherManager.mu.RUnlock()
if !exists {
t.Error("Instance should be registered even if watchers fail to start")
}
// Test stopping watchers
watcherManager.StopWatchers("test-instance")
// Check instance is removed
watcherManager.mu.RLock()
_, exists = watcherManager.watchers["test-instance"]
watcherManager.mu.RUnlock()
if exists {
t.Error("Instance should be removed after stopping watchers")
}
}
func TestWatcherManagerMultipleInstances(t *testing.T) {
sseManager := NewManager()
watcherManager := NewWatcherManager(sseManager)
// Start watchers for multiple instances
_ = watcherManager.StartWatchers("instance-1", "/tmp/kubeconfig1", "/tmp/talosconfig1", "192.168.1.11")
_ = watcherManager.StartWatchers("instance-2", "/tmp/kubeconfig2", "/tmp/talosconfig2", "192.168.1.12")
// Check both instances are registered
watcherManager.mu.RLock()
count := len(watcherManager.watchers)
watcherManager.mu.RUnlock()
if count != 2 {
t.Errorf("Expected 2 instances registered, got %d", count)
}
// Stop watchers for each instance
watcherManager.StopWatchers("instance-1")
watcherManager.StopWatchers("instance-2")
// Check all instances are removed
watcherManager.mu.RLock()
count = len(watcherManager.watchers)
watcherManager.mu.RUnlock()
if count != 0 {
t.Errorf("Expected 0 instances after stopping all, got %d", count)
}
}
func TestInvalidJSONHandling(t *testing.T) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
// Register a client
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Try parsing invalid JSON
invalidJSON := `{invalid json}`
watcher.parsePodEvent([]byte(invalidJSON), "test-instance")
// Should not receive any event
select {
case <-client.Channel:
t.Error("Should not receive event for invalid JSON")
case <-time.After(50 * time.Millisecond):
// Expected - no event for invalid JSON
}
}
func TestEmptyEventHandling(t *testing.T) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
// Register a client
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Try parsing empty JSON
emptyJSON := `{}`
watcher.parsePodEvent([]byte(emptyJSON), "test-instance")
// Should not crash, might not produce event
select {
case <-client.Channel:
// If we get an event, make sure it doesn't crash
t.Log("Received event for empty JSON (acceptable)")
case <-time.After(50 * time.Millisecond):
// Also acceptable - no event for empty JSON
}
}
func BenchmarkJSONParsing(b *testing.B) {
manager := NewManager()
watcher := &KubectlWatcher{
instanceName: "test-instance",
kubeconfig: "/tmp/test-kubeconfig",
sseManager: manager,
}
podJSON := `{
"type": "ADDED",
"object": {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test-pod",
"namespace": "default",
"labels": {
"app": "test-app"
}
},
"status": {
"phase": "Running"
}
}
}`
// Register a client to consume events
client := manager.RegisterClient("test-instance", EventFilters{})
defer manager.UnregisterClient(client)
// Consume events in background
go func() {
for range client.Channel {
// Consume events
}
}()
// Benchmark JSON parsing and event creation
b.ResetTimer()
for i := 0; i < b.N; i++ {
watcher.parsePodEvent([]byte(podJSON), "test-instance")
}
}