Files
beacon-server/internal/api/handlers/traces_test.go
MrAlders0n 2f4f92e31e fix(db): tie-safe trace paging, atomic migrations and honest stats windows
GET /traces takes an optional cursorTag so tags sharing a millisecond survive
paging. Migrations run under an advisory lock and record each file in the
same implicit transaction, so a crash can't strand a fresh database.
Overview windowHours counts the hours it covers, hourly observer activity
fills unrolled hours from raw rows, and integration fixtures are UTC.
2026-10-02 16:53:32 -04:00

94 lines
2.7 KiB
Go

// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
)
func TestListTraceTags_OK(t *testing.T) {
r := chi.NewRouter()
r.Get("/traces", listTraceTags(stubReader{
listTraceTags: func(_ context.Context, _ []string, _, _ string, _, _ time.Time, _ time.Time, _ string, _ int32) ([]api.TraceTagSummary, error) {
return []api.TraceTagSummary{{TraceTag: "trace-001"}}, nil
},
}))
req := httptest.NewRequest(http.MethodGet, "/traces", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestListTraceTags_CursorTag(t *testing.T) {
var gotCursor time.Time
var gotTag string
r := chi.NewRouter()
r.Get("/traces", listTraceTags(stubReader{
listTraceTags: func(_ context.Context, _ []string, _, _ string, _, _ time.Time, cursor time.Time, cursorTag string, _ int32) ([]api.TraceTagSummary, error) {
gotCursor, gotTag = cursor, cursorTag
return nil, nil
},
}))
for _, tc := range []struct {
query string
code int
tag string
}{
{"cursor=1700000000000&cursorTag=A1B2C3D4", http.StatusOK, "a1b2c3d4"},
{"cursor=1700000000000", http.StatusOK, ""},
{"cursor=1700000000000&cursorTag=zz", http.StatusBadRequest, ""},
{"cursorTag=a1b2c3d4", http.StatusBadRequest, ""},
} {
gotTag = ""
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/traces?"+tc.query, nil))
if w.Code != tc.code {
t.Errorf("%s: code %d, want %d", tc.query, w.Code, tc.code)
continue
}
if tc.code == http.StatusOK && (gotTag != tc.tag || gotCursor.UnixMilli() != 1700000000000) {
t.Errorf("%s: cursor %v tag %q", tc.query, gotCursor, gotTag)
}
}
}
func TestGetTrace_OK(t *testing.T) {
r := chi.NewRouter()
r.Get("/traces/{tag}", getTrace(stubReader{
getTraceByTag: func(_ context.Context, tag string) (*api.TraceDetail, error) {
return &api.TraceDetail{TraceTag: tag}, nil
},
}))
req := httptest.NewRequest(http.MethodGet, "/traces/trace-001", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestGetTrace_NotFound(t *testing.T) {
r := chi.NewRouter()
r.Get("/traces/{tag}", getTrace(stubReader{
getTraceByTag: func(_ context.Context, _ string) (*api.TraceDetail, error) {
return nil, nil
},
}))
req := httptest.NewRequest(http.MethodGet, "/traces/missing", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d", w.Code)
}
}