107 lines
3.0 KiB
Go
107 lines
3.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bergabruh/stats-gateway/internal/probes"
|
|
)
|
|
|
|
func TestInsertAndQuery(t *testing.T) {
|
|
dir := t.TempDir()
|
|
st, err := Open(filepath.Join(dir, "test.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
|
|
ctx := context.Background()
|
|
r := &probes.Result{
|
|
HTTPLatencyMs: 42.5,
|
|
TCPRttMs: 12.0,
|
|
DNSResolveMs: 3.0,
|
|
TLSHandshakeMs: 25.0,
|
|
Timestamp: time.Now().UTC(),
|
|
}
|
|
if err := st.InsertSample(ctx, "fl", r); err != nil {
|
|
t.Fatalf("insert: %v", err)
|
|
}
|
|
|
|
samples, err := st.QueryRecent(ctx, "fl", 10, time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("query: %v", err)
|
|
}
|
|
if len(samples) != 1 {
|
|
t.Fatalf("expected 1 sample, got %d", len(samples))
|
|
}
|
|
if samples[0].HTTPLatencyMs != 42.5 {
|
|
t.Errorf("expected latency 42.5, got %f", samples[0].HTTPLatencyMs)
|
|
}
|
|
}
|
|
|
|
func TestQueryRange(t *testing.T) {
|
|
dir := t.TempDir()
|
|
st, err := Open(filepath.Join(dir, "test.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
|
|
ctx := context.Background()
|
|
now := time.Now().UTC().Truncate(time.Second)
|
|
for i, dt := range []time.Duration{-2 * time.Hour, -1 * time.Hour, 0, 1 * time.Hour} {
|
|
r := &probes.Result{HTTPLatencyMs: float64(10 + i), Timestamp: now.Add(dt)}
|
|
if err := st.InsertSample(ctx, "fl", r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
got, err := st.QueryRange(ctx, "fl", now.Add(-90*time.Minute), now.Add(30*time.Minute))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// expect 2 samples: -1h and 0
|
|
if len(got) != 2 {
|
|
t.Fatalf("expected 2, got %d", len(got))
|
|
}
|
|
}
|
|
|
|
// TestQueryRange_SubsecondOrdering verifies fixed-width nanosecond format —
|
|
// regression guard for RFC3339Nano trailing-zero stripping bug.
|
|
func TestQueryRange_SubsecondOrdering(t *testing.T) {
|
|
dir := t.TempDir()
|
|
st, err := Open(filepath.Join(dir, "test.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
|
|
ctx := context.Background()
|
|
base := time.Date(2026, 5, 5, 12, 0, 0, 0, time.UTC)
|
|
// Two timestamps where lexical ordering of RFC3339Nano disagrees with chronology:
|
|
// t1 = base + 100ms → RFC3339Nano formats as "...:00.1Z"
|
|
// t2 = base + 100ms + 1ns → RFC3339Nano formats as "...:00.100000001Z"
|
|
// Lexically ".1Z" > ".100000001Z" because 'Z' > '0', but t1 < t2.
|
|
t1 := base.Add(100 * time.Millisecond)
|
|
t2 := base.Add(100*time.Millisecond + time.Nanosecond)
|
|
for _, ts := range []time.Time{t1, t2} {
|
|
if err := st.InsertSample(ctx, "fl", &probes.Result{HTTPLatencyMs: 1.0, Timestamp: ts}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Range starting just before t1 must include both samples.
|
|
got, err := st.QueryRange(ctx, "fl", base.Add(50*time.Millisecond), base.Add(200*time.Millisecond))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("expected 2 samples in [t-50ms, t+200ms), got %d — lexical ordering bug?", len(got))
|
|
}
|
|
// Verify chronological ordering preserved on read.
|
|
if !got[0].Timestamp.Before(got[1].Timestamp) {
|
|
t.Errorf("expected got[0] < got[1] chronologically; got[0]=%v got[1]=%v", got[0].Timestamp, got[1].Timestamp)
|
|
}
|
|
}
|