package utils
import (
"testing"
"time"
)
var testLocationAuckland = struct {
lat float64
lon float64
}{
lat: -36.8485,
lon: 174.7633,
}
var testLocationLondon = struct {
lat float64
lon float64
}{
lat: 51.5074,
lon: -0.1278,
}
func TestCalculateAstronomicalData(t *testing.T) {
t.Run("should return valid types for all fields", func(t *testing.T) {
winterMidnight := parseTime(t, "2024-06-15T12:00:00Z") duration := 60.0
result := CalculateAstronomicalData(winterMidnight, duration, testLocationAuckland.lat, testLocationAuckland.lon)
if result.MoonPhase < 0 || result.MoonPhase > 1 {
t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
}
})
t.Run("should return false for solar night during daytime hours", func(t *testing.T) {
summerMidday := parseTime(t, "2024-12-15T00:00:00Z") duration := 60.0
result := CalculateAstronomicalData(summerMidday, duration, testLocationAuckland.lat, testLocationAuckland.lon)
if result.SolarNight {
t.Error("Expected SolarNight to be false during daytime")
}
if result.CivilNight {
t.Error("Expected CivilNight to be false during daytime")
}
})
t.Run("should handle different durations correctly", func(t *testing.T) {
timestamp := parseTime(t, "2024-06-15T10:00:00Z")
shortDuration := 30.0 longDuration := 3600.0
shortResult := CalculateAstronomicalData(timestamp, shortDuration, testLocationAuckland.lat, testLocationAuckland.lon)
longResult := CalculateAstronomicalData(timestamp, longDuration, testLocationAuckland.lat, testLocationAuckland.lon)
if shortResult.MoonPhase < 0 || shortResult.MoonPhase > 1 {
t.Errorf("Short duration moon phase out of range: %f", shortResult.MoonPhase)
}
if longResult.MoonPhase < 0 || longResult.MoonPhase > 1 {
t.Errorf("Long duration moon phase out of range: %f", longResult.MoonPhase)
}
})
t.Run("should calculate midpoint time correctly", func(t *testing.T) {
startTime := parseTime(t, "2024-06-15T10:00:00Z")
duration := 7200.0
result := CalculateAstronomicalData(startTime, duration, testLocationAuckland.lat, testLocationAuckland.lon)
_ = result.SolarNight
_ = result.CivilNight
})
t.Run("should handle different geographical locations", func(t *testing.T) {
timestamp := parseTime(t, "2024-06-15T12:00:00Z") duration := 60.0
aucklandResult := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)
londonResult := CalculateAstronomicalData(timestamp, duration, testLocationLondon.lat, testLocationLondon.lon)
_ = aucklandResult.SolarNight
_ = londonResult.SolarNight
})
t.Run("should return valid moon phase values", func(t *testing.T) {
timestamp := parseTime(t, "2024-06-15T12:00:00Z")
duration := 60.0
result := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)
if result.MoonPhase < 0 || result.MoonPhase > 1 {
t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
}
})
t.Run("should handle edge cases with very short durations", func(t *testing.T) {
timestamp := parseTime(t, "2024-06-15T12:00:00Z")
duration := 0.1
result := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)
if result.MoonPhase < 0 || result.MoonPhase > 1 {
t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
}
})
t.Run("should handle edge cases with very long durations", func(t *testing.T) {
timestamp := parseTime(t, "2024-06-15T12:00:00Z")
duration := 86400.0
result := CalculateAstronomicalData(timestamp, duration, testLocationAuckland.lat, testLocationAuckland.lon)
if result.MoonPhase < 0 || result.MoonPhase > 1 {
t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
}
})
}
func TestBooleanLogicValidation(t *testing.T) {
t.Run("should never return invalid values for valid inputs", func(t *testing.T) {
testCases := []string{
"2024-06-15T06:00:00Z", "2024-06-15T12:00:00Z", "2024-06-15T18:00:00Z", "2024-12-15T06:00:00Z", "2024-12-15T12:00:00Z", "2024-12-15T18:00:00Z", }
for _, timestamp := range testCases {
t.Run(timestamp, func(t *testing.T) {
ts := parseTime(t, timestamp)
result := CalculateAstronomicalData(ts, 60, testLocationAuckland.lat, testLocationAuckland.lon)
_ = result.SolarNight
_ = result.CivilNight
if result.MoonPhase < 0 || result.MoonPhase > 1 {
t.Errorf("MoonPhase out of range: got %f, want 0-1", result.MoonPhase)
}
})
}
})
t.Run("should return false for daytime recordings", func(t *testing.T) {
summerMidday := parseTime(t, "2024-12-15T00:30:00Z") duration := 60.0
result := CalculateAstronomicalData(summerMidday, duration, testLocationAuckland.lat, testLocationAuckland.lon)
if result.SolarNight && result.CivilNight {
t.Logf("Note: Both SolarNight and CivilNight are true (may be valid depending on season)")
}
})
t.Run("should return true for nighttime recordings", func(t *testing.T) {
winterMidnight := parseTime(t, "2024-06-15T12:30:00Z") duration := 60.0
result := CalculateAstronomicalData(winterMidnight, duration, testLocationAuckland.lat, testLocationAuckland.lon)
_ = result.SolarNight
_ = result.CivilNight
})
}
func TestCalculateMidpointTime(t *testing.T) {
t.Run("should calculate midpoint correctly", func(t *testing.T) {
startTime := parseTime(t, "2024-06-15T10:00:00Z")
duration := 3600.0
midpoint := CalculateMidpointTime(startTime, duration)
expected := parseTime(t, "2024-06-15T10:30:00Z")
if !midpoint.Equal(expected) {
t.Errorf("Midpoint incorrect: got %v, want %v", midpoint, expected)
}
})
t.Run("should handle short durations", func(t *testing.T) {
startTime := parseTime(t, "2024-06-15T10:00:00Z")
duration := 10.0
midpoint := CalculateMidpointTime(startTime, duration)
expected := parseTime(t, "2024-06-15T10:00:05Z")
if !midpoint.Equal(expected) {
t.Errorf("Midpoint incorrect: got %v, want %v", midpoint, expected)
}
})
}
func parseTime(t *testing.T, s string) time.Time {
t.Helper()
parsed, err := time.Parse(time.RFC3339, s)
if err != nil {
t.Fatalf("Failed to parse time %s: %v", s, err)
}
return parsed
}