213 lines
6.9 KiB
Go
213 lines
6.9 KiB
Go
package webui
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestChartLegendNumber(t *testing.T) {
|
|
tests := []struct {
|
|
in float64
|
|
want string
|
|
}{
|
|
{in: 0.4, want: "0"},
|
|
{in: 61.5, want: "62"},
|
|
{in: 999.4, want: "999"},
|
|
{in: 1200, want: "1,2k"},
|
|
{in: 1250, want: "1,25k"},
|
|
{in: 1310, want: "1,31k"},
|
|
{in: 1500, want: "1,5k"},
|
|
{in: 2600, want: "2,6k"},
|
|
{in: 10200, want: "10k"},
|
|
}
|
|
for _, tc := range tests {
|
|
if got := chartLegendNumber(tc.in); got != tc.want {
|
|
t.Fatalf("chartLegendNumber(%v)=%q want %q", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRootRendersDashboard(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "audit.json")
|
|
exportDir := filepath.Join(dir, "export")
|
|
if err := os.MkdirAll(exportDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(`{"collected_at":"2026-03-15T00:00:00Z","hardware":{"board":{"serial_number":"SERIAL-OLD"}}}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := NewHandler(HandlerOptions{
|
|
Title: "Bee Hardware Audit",
|
|
AuditPath: path,
|
|
ExportDir: exportDir,
|
|
})
|
|
|
|
first := httptest.NewRecorder()
|
|
handler.ServeHTTP(first, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("first status=%d", first.Code)
|
|
}
|
|
// Dashboard should contain the audit nav link and hardware summary
|
|
if !strings.Contains(first.Body.String(), `href="/audit"`) {
|
|
t.Fatalf("first body missing audit nav link: %s", first.Body.String())
|
|
}
|
|
if !strings.Contains(first.Body.String(), `/viewer`) {
|
|
t.Fatalf("first body missing viewer link: %s", first.Body.String())
|
|
}
|
|
if got := first.Header().Get("Cache-Control"); got != "no-store" {
|
|
t.Fatalf("first cache-control=%q", got)
|
|
}
|
|
|
|
if err := os.WriteFile(path, []byte(`{"collected_at":"2026-03-15T00:05:00Z","hardware":{"board":{"serial_number":"SERIAL-NEW"}}}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
second := httptest.NewRecorder()
|
|
handler.ServeHTTP(second, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
if second.Code != http.StatusOK {
|
|
t.Fatalf("second status=%d", second.Code)
|
|
}
|
|
if !strings.Contains(second.Body.String(), `Hardware Summary`) {
|
|
t.Fatalf("second body missing hardware summary: %s", second.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAuditPageRendersViewerFrameAndActions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "audit.json")
|
|
if err := os.WriteFile(path, []byte(`{"collected_at":"2026-03-15T00:00:00Z"}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := NewHandler(HandlerOptions{AuditPath: path})
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/audit", nil))
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d", rec.Code)
|
|
}
|
|
body := rec.Body.String()
|
|
if !strings.Contains(body, `iframe class="viewer-frame" src="/viewer"`) {
|
|
t.Fatalf("audit page missing viewer frame: %s", body)
|
|
}
|
|
if !strings.Contains(body, `openAuditModal()`) {
|
|
t.Fatalf("audit page missing action modal trigger: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestViewerRendersLatestSnapshot(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "audit.json")
|
|
if err := os.WriteFile(path, []byte(`{"collected_at":"2026-03-15T00:00:00Z","hardware":{"board":{"serial_number":"SERIAL-OLD"}}}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := NewHandler(HandlerOptions{AuditPath: path})
|
|
first := httptest.NewRecorder()
|
|
handler.ServeHTTP(first, httptest.NewRequest(http.MethodGet, "/viewer", nil))
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("first status=%d", first.Code)
|
|
}
|
|
if !strings.Contains(first.Body.String(), "SERIAL-OLD") {
|
|
t.Fatalf("viewer body missing old serial: %s", first.Body.String())
|
|
}
|
|
|
|
if err := os.WriteFile(path, []byte(`{"collected_at":"2026-03-15T00:05:00Z","hardware":{"board":{"serial_number":"SERIAL-NEW"}}}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
second := httptest.NewRecorder()
|
|
handler.ServeHTTP(second, httptest.NewRequest(http.MethodGet, "/viewer", nil))
|
|
if second.Code != http.StatusOK {
|
|
t.Fatalf("second status=%d", second.Code)
|
|
}
|
|
if !strings.Contains(second.Body.String(), "SERIAL-NEW") {
|
|
t.Fatalf("viewer body missing new serial: %s", second.Body.String())
|
|
}
|
|
if strings.Contains(second.Body.String(), "SERIAL-OLD") {
|
|
t.Fatalf("viewer body still contains old serial: %s", second.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAuditJSONServesLatestSnapshot(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "audit.json")
|
|
body := `{"hardware":{"board":{"serial_number":"SERIAL-API"}}}`
|
|
if err := os.WriteFile(path, []byte(body), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := NewHandler(HandlerOptions{AuditPath: path})
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/audit.json", nil))
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d", rec.Code)
|
|
}
|
|
if !strings.Contains(rec.Body.String(), "SERIAL-API") {
|
|
t.Fatalf("body missing expected serial: %s", rec.Body.String())
|
|
}
|
|
if got := rec.Header().Get("Content-Type"); !strings.Contains(got, "application/json") {
|
|
t.Fatalf("content-type=%q", got)
|
|
}
|
|
}
|
|
|
|
func TestMissingAuditJSONReturnsNotFound(t *testing.T) {
|
|
handler := NewHandler(HandlerOptions{AuditPath: "/missing/audit.json"})
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/audit.json", nil))
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("status=%d want %d", rec.Code, http.StatusNotFound)
|
|
}
|
|
}
|
|
|
|
func TestSupportBundleEndpointReturnsArchive(t *testing.T) {
|
|
dir := t.TempDir()
|
|
exportDir := filepath.Join(dir, "export")
|
|
if err := os.MkdirAll(exportDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(exportDir, "bee-audit.log"), []byte("audit log"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := NewHandler(HandlerOptions{ExportDir: exportDir})
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/export/support.tar.gz", nil))
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if got := rec.Header().Get("Content-Disposition"); !strings.Contains(got, "attachment;") {
|
|
t.Fatalf("content-disposition=%q", got)
|
|
}
|
|
if rec.Body.Len() == 0 {
|
|
t.Fatal("empty archive body")
|
|
}
|
|
}
|
|
|
|
func TestRuntimeHealthEndpointReturnsJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
exportDir := filepath.Join(dir, "export")
|
|
if err := os.MkdirAll(exportDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body := `{"status":"PARTIAL","checked_at":"2026-03-16T10:00:00Z"}`
|
|
if err := os.WriteFile(filepath.Join(exportDir, "runtime-health.json"), []byte(body), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
handler := NewHandler(HandlerOptions{ExportDir: exportDir})
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/runtime-health.json", nil))
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if strings.TrimSpace(rec.Body.String()) != body {
|
|
t.Fatalf("body=%q want %q", strings.TrimSpace(rec.Body.String()), body)
|
|
}
|
|
}
|