Files
go-ha/internal/scheduling/builder_test.go

354 lines
7.6 KiB
Go

package scheduling
import (
"fmt"
"testing"
"time"
"github.com/Xevion/go-ha/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSchedule(t *testing.T) {
builder := NewSchedule()
assert.NotNil(t, builder)
assert.Empty(t, builder.errors)
assert.Empty(t, builder.triggers)
assert.NotNil(t, builder.hashes)
}
func TestDailyScheduleBuilder_OnFixedTime(t *testing.T) {
tests := []struct {
name string
hour int
minute int
expectError bool
}{
{
name: "valid time",
hour: 12,
minute: 30,
expectError: false,
},
{
name: "midnight",
hour: 0,
minute: 0,
expectError: false,
},
{
name: "invalid hour negative",
hour: -1,
minute: 30,
expectError: true,
},
{
name: "invalid hour too high",
hour: 24,
minute: 30,
expectError: true,
},
{
name: "invalid minute negative",
hour: 12,
minute: -1,
expectError: true,
},
{
name: "invalid minute too high",
hour: 12,
minute: 60,
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := NewSchedule()
result := builder.OnFixedTime(tt.hour, tt.minute)
assert.Equal(t, builder, result) // Should return self for chaining
if tt.expectError {
assert.Len(t, builder.errors, 1)
} else {
assert.Empty(t, builder.errors)
assert.Len(t, builder.triggers, 1)
}
})
}
}
func TestDailyScheduleBuilder_OnSunrise(t *testing.T) {
tests := []struct {
name string
offset []types.DurationString
expectError bool
}{
{
name: "with offset",
offset: []types.DurationString{"30m"},
expectError: false,
},
{
name: "with negative offset",
offset: []types.DurationString{"-1h"},
expectError: false,
},
{
name: "no offset",
offset: []types.DurationString{},
expectError: true,
},
{
name: "invalid duration",
offset: []types.DurationString{"invalid"},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := NewSchedule()
result := builder.OnSunrise(tt.offset...)
assert.Equal(t, builder, result) // Should return self for chaining
if tt.expectError {
assert.Len(t, builder.errors, 1)
} else {
assert.Empty(t, builder.errors)
assert.Len(t, builder.triggers, 1)
}
})
}
}
func TestDailyScheduleBuilder_OnSunset(t *testing.T) {
tests := []struct {
name string
offset []types.DurationString
expectError bool
}{
{
name: "with offset",
offset: []types.DurationString{"1h"},
expectError: false,
},
{
name: "with negative offset",
offset: []types.DurationString{"-30m"},
expectError: false,
},
{
name: "no offset",
offset: []types.DurationString{},
expectError: true,
},
{
name: "invalid duration",
offset: []types.DurationString{"invalid"},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := NewSchedule()
result := builder.OnSunset(tt.offset...)
assert.Equal(t, builder, result) // Should return self for chaining
if tt.expectError {
assert.Len(t, builder.errors, 1)
} else {
assert.Empty(t, builder.errors)
assert.Len(t, builder.triggers, 1)
}
})
}
}
func TestDailyScheduleBuilder_DuplicateTriggers(t *testing.T) {
builder := NewSchedule()
// Add the same fixed time trigger twice
builder.OnFixedTime(12, 30)
builder.OnFixedTime(12, 30)
assert.Len(t, builder.errors, 1)
assert.Len(t, builder.triggers, 1) // Only one should be added
assert.Contains(t, builder.errors[0].Error(), "duplicate trigger")
}
func TestDailyScheduleBuilder_Build_Success(t *testing.T) {
tests := []struct {
name string
setupBuilder func(*DailyScheduleBuilder)
expectedType string
expectedCount int
}{
{
name: "single fixed time trigger",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnFixedTime(12, 30)
},
expectedType: "*scheduling.FixedTimeTrigger",
expectedCount: 1,
},
{
name: "single sunrise trigger",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnSunrise("30m")
},
expectedType: "*scheduling.SunTrigger",
expectedCount: 1,
},
{
name: "multiple triggers",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnFixedTime(8, 0)
b.OnFixedTime(12, 0)
b.OnSunrise("1h")
},
expectedType: "*scheduling.CompositeDailySchedule",
expectedCount: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := NewSchedule()
tt.setupBuilder(builder)
trigger, err := builder.Build()
require.NoError(t, err)
require.NotNil(t, trigger)
assert.Equal(t, tt.expectedType, fmt.Sprintf("%T", trigger))
// Test that the trigger works
now := time.Date(2025, 8, 2, 10, 0, 0, 0, time.Local)
result := trigger.NextTime(now)
assert.NotNil(t, result)
})
}
}
func TestDailyScheduleBuilder_Build_Errors(t *testing.T) {
tests := []struct {
name string
setupBuilder func(*DailyScheduleBuilder)
expectError bool
}{
{
name: "no triggers",
setupBuilder: func(b *DailyScheduleBuilder) {
// Don't add any triggers
},
expectError: true,
},
{
name: "invalid hour",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnFixedTime(25, 0) // Invalid hour
},
expectError: true,
},
{
name: "invalid minute",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnFixedTime(12, 60) // Invalid minute
},
expectError: true,
},
{
name: "no offset for sun trigger",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnSunrise() // No offset
},
expectError: true,
},
{
name: "invalid duration",
setupBuilder: func(b *DailyScheduleBuilder) {
b.OnSunset("invalid") // Invalid duration
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := NewSchedule()
tt.setupBuilder(builder)
trigger, err := builder.Build()
if tt.expectError {
assert.Error(t, err)
assert.Nil(t, trigger)
} else {
assert.NoError(t, err)
assert.NotNil(t, trigger)
}
})
}
}
func TestDailyScheduleBuilder_Chaining(t *testing.T) {
builder := NewSchedule()
// Test method chaining
result := builder.
OnFixedTime(8, 0).
OnFixedTime(12, 0).
OnSunrise("30m")
assert.Equal(t, builder, result)
assert.Len(t, builder.triggers, 3)
assert.Empty(t, builder.errors)
}
func TestDailyScheduleBuilder_NextTime_Integration(t *testing.T) {
builder := NewSchedule()
builder.OnFixedTime(8, 0).
OnFixedTime(12, 0).
OnFixedTime(18, 0)
trigger, err := builder.Build()
require.NoError(t, err)
// Test at different times
tests := []struct {
name string
now time.Time
expected time.Time
}{
{
name: "before all triggers",
now: time.Date(2025, 8, 2, 6, 0, 0, 0, time.Local),
expected: time.Date(2025, 8, 2, 8, 0, 0, 0, time.Local),
},
{
name: "between triggers",
now: time.Date(2025, 8, 2, 10, 0, 0, 0, time.Local),
expected: time.Date(2025, 8, 2, 12, 0, 0, 0, time.Local),
},
{
name: "after all triggers",
now: time.Date(2025, 8, 2, 20, 0, 0, 0, time.Local),
expected: time.Date(2025, 8, 3, 8, 0, 0, 0, time.Local),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := trigger.NextTime(tt.now)
require.NotNil(t, result)
assert.Equal(t, tt.expected, *result)
})
}
}