Fixed few memory leaks. Implement testing of the functionality.

This commit is contained in:
Kalzu Rekku
2026-01-08 18:55:32 +02:00
parent c663ec0431
commit 1130b7fb8c
10 changed files with 1334 additions and 13 deletions

150
ping_service_test.go Normal file
View File

@@ -0,0 +1,150 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
// TestHTTPClientTimeout verifies that the HTTP client has a timeout configured
func TestHTTPClientTimeout(t *testing.T) {
if httpClient.Timeout == 0 {
t.Error("HTTP client timeout is not configured")
}
expectedTimeout := 30 * time.Second
if httpClient.Timeout != expectedTimeout {
t.Errorf("HTTP client timeout = %v, want %v", httpClient.Timeout, expectedTimeout)
}
}
// TestHTTPClientTimeoutActuallyWorks verifies the timeout actually prevents indefinite hangs
func TestHTTPClientTimeoutActuallyWorks(t *testing.T) {
// Create a server that delays response longer than timeout
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(35 * time.Second) // Sleep longer than our 30s timeout
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
start := time.Now()
_, err := httpClient.Get(server.URL)
duration := time.Since(start)
if err == nil {
t.Error("Expected timeout error, got nil")
}
// Should timeout in ~30 seconds, give 3s buffer for slow systems
if duration < 28*time.Second || duration > 33*time.Second {
t.Logf("Request took %v (expected ~30s)", duration)
}
}
// TestCooldownCacheBasic verifies basic cooldown functionality
func TestCooldownCacheBasic(t *testing.T) {
cacheMux.Lock()
cooldownCache = make(map[string]time.Time) // Reset
cacheMux.Unlock()
ip := "192.0.2.1"
// First check - should be allowed
if isInCooldown(ip, 10) {
t.Error("IP should not be in cooldown on first check")
}
// Add to cache
cacheMux.Lock()
cooldownCache[ip] = time.Now()
cacheMux.Unlock()
// Second check - should be in cooldown
if !isInCooldown(ip, 10) {
t.Error("IP should be in cooldown after being added")
}
// Wait for cooldown to expire
cacheMux.Lock()
cooldownCache[ip] = time.Now().Add(-11 * time.Minute)
cacheMux.Unlock()
// Third check - should be allowed again
if isInCooldown(ip, 10) {
t.Error("IP should not be in cooldown after expiry")
}
}
// TestCooldownCacheConcurrency verifies thread-safe cache access
func TestCooldownCacheConcurrency(t *testing.T) {
cacheMux.Lock()
cooldownCache = make(map[string]time.Time)
cacheMux.Unlock()
done := make(chan bool)
// Spawn multiple goroutines accessing cache concurrently
for i := 0; i < 10; i++ {
go func(id int) {
for j := 0; j < 100; j++ {
ip := "192.0.2." + string(rune(id))
isInCooldown(ip, 10)
cacheMux.Lock()
cooldownCache[ip] = time.Now()
cacheMux.Unlock()
}
done <- true
}(i)
}
// Wait for all goroutines
for i := 0; i < 10; i++ {
<-done
}
// If we got here without a race condition, test passes
}
// Helper function from ping_service.go
func isInCooldown(ip string, cooldownMinutes int) bool {
cacheMux.Lock()
defer cacheMux.Unlock()
lastPing, exists := cooldownCache[ip]
if !exists {
return false
}
elapsed := time.Since(lastPing)
cooldownDuration := time.Duration(cooldownMinutes) * time.Minute
return elapsed < cooldownDuration
}
// TestConfigParsing verifies config file parsing works correctly
func TestConfigDefaults(t *testing.T) {
config := Config{
IntervalSeconds: 30,
CooldownMinutes: 10,
EnableTraceroute: true,
TracerouteMaxHops: 30,
HealthCheckPort: 8090,
}
if config.IntervalSeconds <= 0 {
t.Error("IntervalSeconds should be positive")
}
if config.CooldownMinutes <= 0 {
t.Error("CooldownMinutes should be positive")
}
if config.TracerouteMaxHops <= 0 || config.TracerouteMaxHops > 255 {
t.Error("TracerouteMaxHops should be between 1 and 255")
}
if config.HealthCheckPort <= 0 || config.HealthCheckPort > 65535 {
t.Error("HealthCheckPort should be between 1 and 65535")
}
}