mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
Introduce `CalculateTrendFloatWithBaseline` to correctly handle scenarios where previous period metrics (Error Rate, Latency) are zero or missing. This prevents arithmetic errors and distinguishes between "new" data and actual increases ("up") when starting from zero.
Also updates the admin panel dashboard documentation to reflect current project status.
320 lines
7.5 KiB
Go
320 lines
7.5 KiB
Go
package api
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCalculateTrend(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
current int64
|
|
previous int64
|
|
wantDelta *float64
|
|
wantDirection string
|
|
}{
|
|
{
|
|
name: "zero previous, zero current - stable",
|
|
current: 0,
|
|
previous: 0,
|
|
wantDelta: nil,
|
|
wantDirection: "stable",
|
|
},
|
|
{
|
|
name: "zero previous, positive current - new",
|
|
current: 100,
|
|
previous: 0,
|
|
wantDelta: nil,
|
|
wantDirection: "new",
|
|
},
|
|
{
|
|
name: "100% increase - up",
|
|
current: 200,
|
|
previous: 100,
|
|
wantDelta: ptr(100.0),
|
|
wantDirection: "up",
|
|
},
|
|
{
|
|
name: "50% decrease - down",
|
|
current: 50,
|
|
previous: 100,
|
|
wantDelta: ptr(-50.0),
|
|
wantDirection: "down",
|
|
},
|
|
{
|
|
name: "0.3% increase - stable",
|
|
current: 1003,
|
|
previous: 1000,
|
|
wantDelta: ptr(0.3),
|
|
wantDirection: "stable",
|
|
},
|
|
{
|
|
name: "0.5% increase - stable (boundary)",
|
|
current: 1005,
|
|
previous: 1000,
|
|
wantDelta: ptr(0.5),
|
|
wantDirection: "stable",
|
|
},
|
|
{
|
|
name: "0.6% increase - up",
|
|
current: 1006,
|
|
previous: 1000,
|
|
wantDelta: ptr(0.6),
|
|
wantDirection: "up",
|
|
},
|
|
{
|
|
name: "0.6% decrease - down",
|
|
current: 994,
|
|
previous: 1000,
|
|
wantDelta: ptr(-0.6),
|
|
wantDirection: "down",
|
|
},
|
|
{
|
|
name: "no change - stable",
|
|
current: 100,
|
|
previous: 100,
|
|
wantDelta: ptr(0.0),
|
|
wantDirection: "stable",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := CalculateTrend(tt.current, tt.previous)
|
|
|
|
if tt.wantDelta == nil {
|
|
if got.Delta != nil {
|
|
t.Errorf("expected nil delta, got %v", *got.Delta)
|
|
}
|
|
} else {
|
|
if got.Delta == nil {
|
|
t.Errorf("expected delta %v, got nil", *tt.wantDelta)
|
|
} else if *got.Delta != *tt.wantDelta {
|
|
t.Errorf("expected delta %v, got %v", *tt.wantDelta, *got.Delta)
|
|
}
|
|
}
|
|
|
|
if got.Direction != tt.wantDirection {
|
|
t.Errorf("expected direction %q, got %q", tt.wantDirection, got.Direction)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateTrendFloat(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
current float64
|
|
previous float64
|
|
wantDelta *float64
|
|
wantDirection string
|
|
}{
|
|
{
|
|
name: "zero previous, zero current - stable",
|
|
current: 0,
|
|
previous: 0,
|
|
wantDelta: nil,
|
|
wantDirection: "stable",
|
|
},
|
|
{
|
|
name: "zero previous, positive current - new",
|
|
current: 0.5,
|
|
previous: 0,
|
|
wantDelta: nil,
|
|
wantDirection: "new",
|
|
},
|
|
{
|
|
name: "50% increase - up",
|
|
current: 0.15,
|
|
previous: 0.10,
|
|
wantDelta: ptr(50.0),
|
|
wantDirection: "up",
|
|
},
|
|
{
|
|
name: "33.3% decrease - down",
|
|
current: 0.10,
|
|
previous: 0.15,
|
|
wantDelta: ptr(-33.3),
|
|
wantDirection: "down",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := CalculateTrendFloat(tt.current, tt.previous)
|
|
|
|
if tt.wantDelta == nil {
|
|
if got.Delta != nil {
|
|
t.Errorf("expected nil delta, got %v", *got.Delta)
|
|
}
|
|
} else {
|
|
if got.Delta == nil {
|
|
t.Errorf("expected delta %v, got nil", *tt.wantDelta)
|
|
} else if *got.Delta != *tt.wantDelta {
|
|
t.Errorf("expected delta %v, got %v", *tt.wantDelta, *got.Delta)
|
|
}
|
|
}
|
|
|
|
if got.Direction != tt.wantDirection {
|
|
t.Errorf("expected direction %q, got %q", tt.wantDirection, got.Direction)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateTrendFloatWithBaseline(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
current float64
|
|
previous float64
|
|
hasBaseline bool
|
|
wantDelta *float64
|
|
wantDirection string
|
|
}{
|
|
{
|
|
name: "no baseline, zero current",
|
|
current: 0,
|
|
previous: 0,
|
|
hasBaseline: false,
|
|
wantDelta: nil,
|
|
wantDirection: "stable",
|
|
},
|
|
{
|
|
name: "no baseline, positive current",
|
|
current: 0.2,
|
|
previous: 0,
|
|
hasBaseline: false,
|
|
wantDelta: nil,
|
|
wantDirection: "new",
|
|
},
|
|
{
|
|
name: "baseline present, zero previous and current",
|
|
current: 0,
|
|
previous: 0,
|
|
hasBaseline: true,
|
|
wantDelta: nil,
|
|
wantDirection: "stable",
|
|
},
|
|
{
|
|
name: "baseline present, zero previous to positive",
|
|
current: 0.2,
|
|
previous: 0,
|
|
hasBaseline: true,
|
|
wantDelta: nil,
|
|
wantDirection: "up",
|
|
},
|
|
{
|
|
name: "baseline present, increase",
|
|
current: 0.15,
|
|
previous: 0.10,
|
|
hasBaseline: true,
|
|
wantDelta: ptr(50.0),
|
|
wantDirection: "up",
|
|
},
|
|
{
|
|
name: "baseline present, no change",
|
|
current: 0.10,
|
|
previous: 0.10,
|
|
hasBaseline: true,
|
|
wantDelta: ptr(0.0),
|
|
wantDirection: "stable",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := CalculateTrendFloatWithBaseline(tt.current, tt.previous, tt.hasBaseline)
|
|
|
|
if tt.wantDelta == nil {
|
|
if got.Delta != nil {
|
|
t.Errorf("expected nil delta, got %v", *got.Delta)
|
|
}
|
|
} else {
|
|
if got.Delta == nil {
|
|
t.Errorf("expected delta %v, got nil", *tt.wantDelta)
|
|
} else if *got.Delta != *tt.wantDelta {
|
|
t.Errorf("expected delta %v, got %v", *tt.wantDelta, *got.Delta)
|
|
}
|
|
}
|
|
|
|
if got.Direction != tt.wantDirection {
|
|
t.Errorf("expected direction %q, got %q", tt.wantDirection, got.Direction)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreviousPeriodWindow(t *testing.T) {
|
|
// These tests verify the structure of the period calculation
|
|
// Not testing exact timestamps since they depend on current time
|
|
|
|
tests := []struct {
|
|
period string
|
|
expectZero bool
|
|
expectWindow bool
|
|
}{
|
|
{"today", false, true},
|
|
{"last7d", false, true},
|
|
{"last30d", false, true},
|
|
{"week", false, true},
|
|
{"month", false, true},
|
|
{"all", true, false},
|
|
{"invalid", true, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.period, func(t *testing.T) {
|
|
start, end := previousPeriodWindow(tt.period)
|
|
|
|
if tt.expectZero {
|
|
if !start.IsZero() || !end.IsZero() {
|
|
t.Errorf("expected zero times for period %q", tt.period)
|
|
}
|
|
} else {
|
|
if start.IsZero() || end.IsZero() {
|
|
t.Errorf("expected non-zero times for period %q", tt.period)
|
|
}
|
|
if !start.Before(end) {
|
|
t.Errorf("expected start < end for period %q", tt.period)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreviousPeriodWindowToday(t *testing.T) {
|
|
start, end := previousPeriodWindow("today")
|
|
|
|
// Previous period for "today" should be "yesterday"
|
|
now := time.Now().UTC()
|
|
startOfToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
|
yesterday := startOfToday.AddDate(0, 0, -1)
|
|
|
|
if !start.Equal(yesterday) {
|
|
t.Errorf("expected start to be yesterday (%v), got %v", yesterday, start)
|
|
}
|
|
if !end.Equal(startOfToday) {
|
|
t.Errorf("expected end to be start of today (%v), got %v", startOfToday, end)
|
|
}
|
|
}
|
|
|
|
func TestPreviousPeriodWindowLast7d(t *testing.T) {
|
|
start, end := previousPeriodWindow("last7d")
|
|
|
|
now := time.Now().UTC()
|
|
expected14DaysAgo := now.AddDate(0, 0, -14)
|
|
expected7DaysAgo := now.AddDate(0, 0, -7)
|
|
|
|
// Allow 1 second tolerance for test execution time
|
|
if start.Sub(expected14DaysAgo).Abs() > time.Second {
|
|
t.Errorf("expected start ~14 days ago, got %v (expected %v)", start, expected14DaysAgo)
|
|
}
|
|
if end.Sub(expected7DaysAgo).Abs() > time.Second {
|
|
t.Errorf("expected end ~7 days ago, got %v (expected %v)", end, expected7DaysAgo)
|
|
}
|
|
}
|
|
|
|
func ptr(v float64) *float64 {
|
|
return &v
|
|
}
|