mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
- Add `include_trends` query parameter to enable trend calculation - Implement trend comparison logic (delta % and direction) against previous periods - Add support for `last7d`, `last30d`, and `all` time period options - Update `DashboardSummaryResponse` to include optional `trends` field - Add helper functions for custom time window aggregation - Add unit tests for trend calculation and period window logic - Update feature documentation with new parameters and response schemas
238 lines
5.6 KiB
Go
238 lines
5.6 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 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
|
|
}
|