mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-03-30 14:45:52 +00:00
- ingestor: add config_test.go (LoadConfig, env overrides, legacy MQTT) - ingestor: add main_test.go (toFloat64, firstNonEmpty, handleMessage, advertRole) - ingestor: extend decoder_test.go (short buffer errors, edge cases, all payload types) - ingestor: extend db_test.go (empty hash, timestamp updates, BuildPacketData, schema) - server: add config_test.go (LoadConfig, LoadTheme, health thresholds, ResolveDBPath) - server: add helpers_test.go (writeJSON/Error, queryInt, mergeMap, round, percentile, spaHandler) - server: extend db_test.go (all query functions, filters, channel messages, node health) - server: extend routes_test.go (all endpoints, error paths, analytics, observer analytics) - server: extend websocket_test.go (multi-client, buffer full, poller cycle) Coverage: ingestor 48% -> 80%, server 52% -> 90% Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
348 lines
8.6 KiB
Go
348 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestWriteError(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeError(w, 404, "Not found")
|
|
|
|
if w.Code != 404 {
|
|
t.Errorf("expected 404, got %d", w.Code)
|
|
}
|
|
ct := w.Header().Get("Content-Type")
|
|
if ct != "application/json" {
|
|
t.Errorf("expected application/json, got %s", ct)
|
|
}
|
|
var body map[string]string
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
if body["error"] != "Not found" {
|
|
t.Errorf("expected 'Not found', got %s", body["error"])
|
|
}
|
|
}
|
|
|
|
func TestWriteErrorVariousCodes(t *testing.T) {
|
|
tests := []struct {
|
|
code int
|
|
msg string
|
|
}{
|
|
{400, "Bad request"},
|
|
{500, "Internal error"},
|
|
{403, "Forbidden"},
|
|
}
|
|
for _, tc := range tests {
|
|
w := httptest.NewRecorder()
|
|
writeError(w, tc.code, tc.msg)
|
|
if w.Code != tc.code {
|
|
t.Errorf("expected %d, got %d", tc.code, w.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueryInt(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
key string
|
|
def int
|
|
expected int
|
|
}{
|
|
{"valid", "/?limit=25", "limit", 50, 25},
|
|
{"missing", "/?other=5", "limit", 50, 50},
|
|
{"empty", "/?limit=", "limit", 50, 50},
|
|
{"invalid", "/?limit=abc", "limit", 50, 50},
|
|
{"zero", "/?limit=0", "limit", 50, 0},
|
|
{"negative", "/?limit=-1", "limit", 50, -1},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := httptest.NewRequest("GET", tc.url, nil)
|
|
got := queryInt(r, tc.key, tc.def)
|
|
if got != tc.expected {
|
|
t.Errorf("expected %d, got %d", tc.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeMap(t *testing.T) {
|
|
t.Run("basic merge", func(t *testing.T) {
|
|
base := map[string]interface{}{"a": 1, "b": 2}
|
|
overlay := map[string]interface{}{"b": 3, "c": 4}
|
|
result := mergeMap(base, overlay)
|
|
|
|
if result["a"] != 1 {
|
|
t.Errorf("expected 1, got %v", result["a"])
|
|
}
|
|
if result["b"] != 3 {
|
|
t.Errorf("expected 3 (overridden), got %v", result["b"])
|
|
}
|
|
if result["c"] != 4 {
|
|
t.Errorf("expected 4, got %v", result["c"])
|
|
}
|
|
})
|
|
|
|
t.Run("nil overlay", func(t *testing.T) {
|
|
base := map[string]interface{}{"a": 1}
|
|
result := mergeMap(base, nil)
|
|
if result["a"] != 1 {
|
|
t.Errorf("expected 1, got %v", result["a"])
|
|
}
|
|
})
|
|
|
|
t.Run("multiple overlays", func(t *testing.T) {
|
|
base := map[string]interface{}{"a": 1}
|
|
o1 := map[string]interface{}{"b": 2}
|
|
o2 := map[string]interface{}{"c": 3, "a": 10}
|
|
result := mergeMap(base, o1, o2)
|
|
if result["a"] != 10 {
|
|
t.Errorf("expected 10, got %v", result["a"])
|
|
}
|
|
if result["b"] != 2 {
|
|
t.Errorf("expected 2, got %v", result["b"])
|
|
}
|
|
if result["c"] != 3 {
|
|
t.Errorf("expected 3, got %v", result["c"])
|
|
}
|
|
})
|
|
|
|
t.Run("empty base", func(t *testing.T) {
|
|
result := mergeMap(map[string]interface{}{}, map[string]interface{}{"x": 5})
|
|
if result["x"] != 5 {
|
|
t.Errorf("expected 5, got %v", result["x"])
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSafeAvg(t *testing.T) {
|
|
tests := []struct {
|
|
total, count float64
|
|
expected float64
|
|
}{
|
|
{100, 10, 10.0},
|
|
{0, 0, 0},
|
|
{33, 3, 11.0},
|
|
{10, 3, 3.3},
|
|
}
|
|
for _, tc := range tests {
|
|
got := safeAvg(tc.total, tc.count)
|
|
if got != tc.expected {
|
|
t.Errorf("safeAvg(%v, %v) = %v, want %v", tc.total, tc.count, got, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRound(t *testing.T) {
|
|
tests := []struct {
|
|
val float64
|
|
places int
|
|
want float64
|
|
}{
|
|
{3.456, 1, 3.5},
|
|
{3.444, 1, 3.4},
|
|
{3.456, 2, 3.46},
|
|
{0, 1, 0},
|
|
{100.0, 0, 100.0},
|
|
}
|
|
for _, tc := range tests {
|
|
got := round(tc.val, tc.places)
|
|
if got != tc.want {
|
|
t.Errorf("round(%v, %d) = %v, want %v", tc.val, tc.places, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPercentile(t *testing.T) {
|
|
t.Run("empty", func(t *testing.T) {
|
|
if percentile([]float64{}, 0.5) != 0 {
|
|
t.Error("expected 0 for empty slice")
|
|
}
|
|
})
|
|
|
|
t.Run("single element", func(t *testing.T) {
|
|
if percentile([]float64{42}, 0.5) != 42 {
|
|
t.Error("expected 42")
|
|
}
|
|
})
|
|
|
|
t.Run("p50", func(t *testing.T) {
|
|
sorted := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
|
got := percentile(sorted, 0.5)
|
|
if got != 6 {
|
|
t.Errorf("expected 6 for p50, got %v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("p95", func(t *testing.T) {
|
|
sorted := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
|
got := percentile(sorted, 0.95)
|
|
if got != 10 {
|
|
t.Errorf("expected 10 for p95, got %v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("p100 clamps", func(t *testing.T) {
|
|
sorted := []float64{1, 2, 3}
|
|
got := percentile(sorted, 1.0)
|
|
if got != 3 {
|
|
t.Errorf("expected 3 for p100, got %v", got)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSortedCopy(t *testing.T) {
|
|
original := []float64{5, 3, 1, 4, 2}
|
|
sorted := sortedCopy(original)
|
|
|
|
// Original should be unchanged
|
|
if original[0] != 5 {
|
|
t.Error("original should not be modified")
|
|
}
|
|
|
|
expected := []float64{1, 2, 3, 4, 5}
|
|
for i, v := range sorted {
|
|
if v != expected[i] {
|
|
t.Errorf("index %d: expected %v, got %v", i, expected[i], v)
|
|
}
|
|
}
|
|
|
|
// Empty slice
|
|
empty := sortedCopy([]float64{})
|
|
if len(empty) != 0 {
|
|
t.Error("expected empty slice")
|
|
}
|
|
}
|
|
|
|
func TestLastN(t *testing.T) {
|
|
arr := []map[string]interface{}{
|
|
{"id": 1}, {"id": 2}, {"id": 3}, {"id": 4}, {"id": 5},
|
|
}
|
|
|
|
t.Run("n less than length", func(t *testing.T) {
|
|
result := lastN(arr, 3)
|
|
if len(result) != 3 {
|
|
t.Errorf("expected 3, got %d", len(result))
|
|
}
|
|
if result[0]["id"] != 3 {
|
|
t.Errorf("expected id 3, got %v", result[0]["id"])
|
|
}
|
|
})
|
|
|
|
t.Run("n greater than length", func(t *testing.T) {
|
|
result := lastN(arr, 10)
|
|
if len(result) != 5 {
|
|
t.Errorf("expected 5, got %d", len(result))
|
|
}
|
|
})
|
|
|
|
t.Run("n equals length", func(t *testing.T) {
|
|
result := lastN(arr, 5)
|
|
if len(result) != 5 {
|
|
t.Errorf("expected 5, got %d", len(result))
|
|
}
|
|
})
|
|
|
|
t.Run("empty", func(t *testing.T) {
|
|
result := lastN([]map[string]interface{}{}, 5)
|
|
if len(result) != 0 {
|
|
t.Errorf("expected 0, got %d", len(result))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSpaHandler(t *testing.T) {
|
|
// Create a temp directory with test files
|
|
dir := t.TempDir()
|
|
os.WriteFile(filepath.Join(dir, "index.html"), []byte("<html>SPA</html>"), 0644)
|
|
os.WriteFile(filepath.Join(dir, "app.js"), []byte("console.log('app')"), 0644)
|
|
os.WriteFile(filepath.Join(dir, "style.css"), []byte("body{}"), 0644)
|
|
|
|
fs := http.FileServer(http.Dir(dir))
|
|
handler := spaHandler(dir, fs)
|
|
|
|
t.Run("existing JS file with cache control", func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/app.js", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != 200 {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
cc := w.Header().Get("Cache-Control")
|
|
if cc != "no-cache, no-store, must-revalidate" {
|
|
t.Errorf("expected no-cache header for .js, got %s", cc)
|
|
}
|
|
})
|
|
|
|
t.Run("existing CSS file with cache control", func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/style.css", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != 200 {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
cc := w.Header().Get("Cache-Control")
|
|
if cc != "no-cache, no-store, must-revalidate" {
|
|
t.Errorf("expected no-cache header for .css, got %s", cc)
|
|
}
|
|
})
|
|
|
|
t.Run("non-existent file falls back to index.html", func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/some/spa/route", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != 200 {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
body := w.Body.String()
|
|
if body != "<html>SPA</html>" {
|
|
t.Errorf("expected SPA index.html content, got %s", body)
|
|
}
|
|
})
|
|
|
|
t.Run("existing HTML file", func(t *testing.T) {
|
|
// Subdirectory with HTML file to avoid redirect from root /index.html
|
|
subDir := filepath.Join(dir, "sub")
|
|
os.Mkdir(subDir, 0755)
|
|
os.WriteFile(filepath.Join(subDir, "page.html"), []byte("<html>page</html>"), 0644)
|
|
|
|
req := httptest.NewRequest("GET", "/sub/page.html", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != 200 {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
cc := w.Header().Get("Cache-Control")
|
|
if cc != "no-cache, no-store, must-revalidate" {
|
|
t.Errorf("expected no-cache header for .html, got %s", cc)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWriteJSON(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeJSON(w, map[string]interface{}{"key": "value"})
|
|
|
|
if w.Code != 200 {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
ct := w.Header().Get("Content-Type")
|
|
if ct != "application/json" {
|
|
t.Errorf("expected application/json, got %s", ct)
|
|
}
|
|
var body map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
if body["key"] != "value" {
|
|
t.Errorf("expected 'value', got %v", body["key"])
|
|
}
|
|
}
|