utils_test.go•7.21 kB
package utils
import (
	"testing"
	"time"
)
func TestGetTimeRange_TimezoneHandling(t *testing.T) {
	tests := []struct {
		name          string
		params        map[string]interface{}
		wantStartUnix int64
		wantEndUnix   int64
		wantErr       bool
	}{
		{
			name: "ISO timestamps parsed as UTC",
			params: map[string]interface{}{
				"start_time_iso": "2025-06-23 16:00:00",
				"end_time_iso":   "2025-06-23 16:30:00",
			},
			wantStartUnix: 1750694400, // 2025-06-23 16:00:00 UTC
			wantEndUnix:   1750696200, // 2025-06-23 16:30:00 UTC
			wantErr:       false,
		},
		{
			name: "only start_time provided - end time should be start + lookback",
			params: map[string]interface{}{
				"start_time_iso": "2025-06-27 16:00:00",
			},
			wantStartUnix: 1751040000, // 2025-06-27 16:00:00 UTC
			// end time will be current time, so we'll check it separately
			wantErr: false,
		},
		{
			name: "lookback minutes only - no explicit timestamps",
			params: map[string]interface{}{
				"lookback_minutes": float64(30),
			},
			// timestamps will be calculated from current time
			wantErr: false,
		},
		{
			name: "invalid start_time format",
			params: map[string]interface{}{
				"start_time_iso": "invalid-time",
			},
			wantErr: true,
		},
		{
			name: "invalid end_time format",
			params: map[string]interface{}{
				"end_time_iso": "invalid-time",
			},
			wantErr: true,
		},
		{
			name: "start_time after end_time",
			params: map[string]interface{}{
				"start_time_iso": "2025-06-23 17:00:00",
				"end_time_iso":   "2025-06-23 16:00:00",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			start, end, err := GetTimeRange(tt.params, 60)
			if tt.wantErr {
				if err == nil {
					t.Errorf("GetTimeRange() expected error but got none")
				}
				return
			}
			if err != nil {
				t.Errorf("GetTimeRange() unexpected error: %v", err)
				return
			}
			// Check start time if specified
			if tt.wantStartUnix != 0 {
				if start.Unix() != tt.wantStartUnix {
					t.Errorf("GetTimeRange() start time = %d, want %d", start.Unix(), tt.wantStartUnix)
				}
			}
			// Check end time if specified
			if tt.wantEndUnix != 0 {
				if end.Unix() != tt.wantEndUnix {
					t.Errorf("GetTimeRange() end time = %d, want %d", end.Unix(), tt.wantEndUnix)
				}
			}
			// Special checks for edge cases
			if tt.name == "only start_time provided - end time should be start + lookback" {
				// End time should be start time + 60 minutes (default lookback)
				expectedEnd := start.Add(60 * time.Minute)
				if end.Unix() != expectedEnd.Unix() {
					t.Errorf("GetTimeRange() end time = %d, want %d (start + 60min)", end.Unix(), expectedEnd.Unix())
				}
			}
			if tt.name == "lookback minutes only - no explicit timestamps" {
				// Should use current time with 30 minute lookback
				now := time.Now().UTC()
				timeDiff := end.Sub(now)
				if timeDiff < -5*time.Second || timeDiff > 5*time.Second {
					t.Errorf("GetTimeRange() end time should be close to now, got diff: %v", timeDiff)
				}
				expectedDiff := 30 * time.Minute
				actualDiff := end.Sub(start)
				if actualDiff != expectedDiff {
					t.Errorf("GetTimeRange() time difference = %v, want %v", actualDiff, expectedDiff)
				}
			}
		})
	}
}
func TestGetTimeRange_LookbackMinutes(t *testing.T) {
	tests := []struct {
		name                   string
		params                 map[string]interface{}
		defaultLookbackMinutes int
		wantLookbackUsed       int
		wantErr                bool
	}{
		{
			name:                   "default lookback minutes",
			params:                 map[string]interface{}{},
			defaultLookbackMinutes: 30,
			wantLookbackUsed:       30,
			wantErr:                false,
		},
		{
			name: "custom lookback minutes",
			params: map[string]interface{}{
				"lookback_minutes": float64(45),
			},
			defaultLookbackMinutes: 30,
			wantLookbackUsed:       45,
			wantErr:                false,
		},
		{
			name: "lookback too small",
			params: map[string]interface{}{
				"lookback_minutes": float64(0),
			},
			defaultLookbackMinutes: 30,
			wantErr:                true,
		},
		{
			name: "lookback too large",
			params: map[string]interface{}{
				"lookback_minutes": float64(1500), // > 1440
			},
			defaultLookbackMinutes: 30,
			wantErr:                true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			start, end, err := GetTimeRange(tt.params, tt.defaultLookbackMinutes)
			if tt.wantErr {
				if err == nil {
					t.Errorf("GetTimeRange() expected error but got none")
				}
				return
			}
			if err != nil {
				t.Errorf("GetTimeRange() unexpected error: %v", err)
				return
			}
			// Check that the time difference matches expected lookback
			timeDiff := end.Sub(start)
			expectedDiff := time.Duration(tt.wantLookbackUsed) * time.Minute
			if timeDiff != expectedDiff {
				t.Errorf("GetTimeRange() time difference = %v, want %v", timeDiff, expectedDiff)
			}
			// Verify times are in UTC
			if start.Location() != time.UTC {
				t.Errorf("GetTimeRange() start time not in UTC: %v", start.Location())
			}
			if end.Location() != time.UTC {
				t.Errorf("GetTimeRange() end time not in UTC: %v", end.Location())
			}
		})
	}
}
func TestGetTimeRange_UTCConsistency(t *testing.T) {
	// Test that all returned times are consistently in UTC
	params := map[string]interface{}{
		"start_time_iso": "2025-06-23 16:00:00",
		"end_time_iso":   "2025-06-23 16:30:00",
	}
	start, end, err := GetTimeRange(params, 60)
	if err != nil {
		t.Fatalf("GetTimeRange() unexpected error: %v", err)
	}
	if start.Location() != time.UTC {
		t.Errorf("Start time not in UTC timezone: %v", start.Location())
	}
	if end.Location() != time.UTC {
		t.Errorf("End time not in UTC timezone: %v", end.Location())
	}
	// Test the actual scenario from the user's bug report
	// Input: 2025-06-23 16:00:00 should return Unix timestamp 1750694400
	expectedUnixStart := int64(1750694400)
	if start.Unix() != expectedUnixStart {
		t.Errorf("Unix timestamp mismatch: got %d, want %d", start.Unix(), expectedUnixStart)
	}
}
func TestGetTimeRange_TimeRangeValidation(t *testing.T) {
	tests := []struct {
		name    string
		params  map[string]interface{}
		wantErr bool
		errMsg  string
	}{
		{
			name: "time range exceeds 24 hours",
			params: map[string]interface{}{
				"start_time_iso": "2025-06-23 00:00:00",
				"end_time_iso":   "2025-06-24 01:00:00", // 25 hours
			},
			wantErr: true,
			errMsg:  "time range cannot exceed 24 hours",
		},
		{
			name: "valid 24 hour range",
			params: map[string]interface{}{
				"start_time_iso": "2025-06-23 00:00:00",
				"end_time_iso":   "2025-06-24 00:00:00", // exactly 24 hours
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, _, err := GetTimeRange(tt.params, 60)
			if tt.wantErr {
				if err == nil {
					t.Errorf("GetTimeRange() expected error but got none")
					return
				}
				if tt.errMsg != "" && err.Error() != tt.errMsg {
					t.Errorf("GetTimeRange() error = %q, want %q", err.Error(), tt.errMsg)
				}
			} else {
				if err != nil {
					t.Errorf("GetTimeRange() unexpected error: %v", err)
				}
			}
		})
	}
}