106 строки
5.9 KiB
Go
106 строки
5.9 KiB
Go
package models_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"rsgit.ru/rsmon/rsmon/app/models"
|
|
)
|
|
|
|
func TestMaintenanceValidateGeneratesRecurringCron(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
m models.Maintenance
|
|
want string
|
|
}{
|
|
{"interval", models.Maintenance{Title: "interval", Strategy: models.MaintenanceRecurringInterval, StartDate: maintenanceTimePtr(time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)), StartTime: "02:30", IntervalDay: 3, DurationSec: 60, Timezone: "UTC"}, ""},
|
|
{"weekday", models.Maintenance{Title: "weekdays", Strategy: models.MaintenanceRecurringWeekday, StartTime: "02:30", Weekdays: []int64{1, 5}, DurationSec: 60, Timezone: "UTC"}, "30 02 * * 1,5"},
|
|
{"month", models.Maintenance{Title: "month", Strategy: models.MaintenanceRecurringDayOfMonth, StartTime: "02:30", DaysOfMonth: []string{"1", "lastDay1"}, DurationSec: 60, Timezone: "UTC"}, "30 02 1,28-31 * *"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) { require.NoError(t, test.m.Validate()); assert.Equal(t, test.want, test.m.Cron) })
|
|
}
|
|
}
|
|
|
|
func TestMaintenanceIsUnderMaintenanceBoundariesAndTimezone(t *testing.T) {
|
|
start := time.Date(2026, 7, 1, 10, 0, 0, 0, time.UTC)
|
|
end := start.Add(time.Hour)
|
|
single := models.Maintenance{Title: "single", Strategy: models.MaintenanceSingle, StartDate: &start, EndDate: &end, Active: true, Timezone: "UTC"}
|
|
assert.True(t, single.IsUnderMaintenance(start))
|
|
assert.True(t, single.IsUnderMaintenance(end.Add(-time.Nanosecond)))
|
|
assert.False(t, single.IsUnderMaintenance(end))
|
|
cron := models.Maintenance{Title: "moscow", Strategy: models.MaintenanceCron, Cron: "0 12 * * *", DurationSec: 3600, Active: true, Timezone: "Europe/Moscow"}
|
|
require.NoError(t, cron.Validate())
|
|
assert.True(t, cron.IsUnderMaintenance(time.Date(2026, 7, 1, 9, 30, 0, 0, time.UTC)), "12:30 Moscow is 09:30 UTC in July")
|
|
assert.False(t, cron.IsUnderMaintenance(time.Date(2026, 7, 1, 10, 0, 0, 0, time.UTC)))
|
|
}
|
|
|
|
func TestMaintenanceValidationRejectsInvalidSchedules(t *testing.T) {
|
|
badCron := models.Maintenance{Title: "bad", Strategy: models.MaintenanceCron, Cron: "not cron", DurationSec: 1, Timezone: "UTC"}
|
|
badSingle := models.Maintenance{Title: "bad", Strategy: models.MaintenanceSingle, Timezone: "UTC"}
|
|
assert.Error(t, badCron.Validate())
|
|
assert.Error(t, badSingle.Validate())
|
|
}
|
|
|
|
func TestMaintenanceLastDayIsNotEveryDayInExpandedCronRange(t *testing.T) {
|
|
m := models.Maintenance{Title: "last", Strategy: models.MaintenanceRecurringDayOfMonth, StartTime: "12:00", DaysOfMonth: []string{"lastDay1"}, DurationSec: 3600, Active: true, Timezone: "UTC"}
|
|
require.NoError(t, m.Validate())
|
|
assert.False(t, m.IsUnderMaintenance(time.Date(2026, 3, 28, 12, 30, 0, 0, time.UTC)))
|
|
assert.True(t, m.IsUnderMaintenance(time.Date(2026, 3, 31, 12, 30, 0, 0, time.UTC)))
|
|
}
|
|
|
|
func TestMaintenanceRecurringIntervalUsesAnchorAndIntervalDay(t *testing.T) {
|
|
anchor := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
m := models.Maintenance{Title: "every three days", Strategy: models.MaintenanceRecurringInterval, StartDate: &anchor, StartTime: "12:00", IntervalDay: 3, DurationSec: 3600, Active: true, Timezone: "UTC"}
|
|
require.NoError(t, m.Validate())
|
|
assert.True(t, m.IsUnderMaintenance(time.Date(2026, 7, 4, 12, 30, 0, 0, time.UTC)))
|
|
assert.False(t, m.IsUnderMaintenance(time.Date(2026, 7, 5, 12, 30, 0, 0, time.UTC)))
|
|
next := m.NextRun(time.Date(2026, 7, 2, 13, 0, 0, 0, time.UTC))
|
|
require.NotNil(t, next)
|
|
assert.Equal(t, time.Date(2026, 7, 4, 12, 0, 0, 0, time.UTC), *next)
|
|
}
|
|
|
|
func TestMaintenanceRecurringIntervalRequiresAnchorForMultiDayAndPositiveInterval(t *testing.T) {
|
|
m := models.Maintenance{Title: "invalid", Strategy: models.MaintenanceRecurringInterval, StartTime: "12:00", IntervalDay: 2, DurationSec: 60, Timezone: "UTC"}
|
|
assert.Error(t, m.Validate())
|
|
}
|
|
|
|
func TestMaintenanceRecurringIntervalKeepsCivilDayAcrossDST(t *testing.T) {
|
|
loc, err := time.LoadLocation("Europe/Berlin")
|
|
require.NoError(t, err)
|
|
// March 29, 2026 is the spring-forward day in Berlin. The second run is
|
|
// still two civil days after the anchor, not one because a day was 23h.
|
|
anchor := time.Date(2026, 3, 27, 0, 0, 0, 0, loc)
|
|
m := models.Maintenance{Title: "DST", Strategy: models.MaintenanceRecurringInterval, StartDate: &anchor, StartTime: "03:30", IntervalDay: 2, DurationSec: 3600, Active: true, Timezone: "Europe/Berlin"}
|
|
require.NoError(t, m.Validate())
|
|
assert.True(t, m.IsUnderMaintenance(time.Date(2026, 3, 29, 4, 0, 0, 0, loc).UTC()))
|
|
}
|
|
|
|
func TestMaintenanceStartNotificationKeyIncludesRevisionAndOccurrence(t *testing.T) {
|
|
revision := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC)
|
|
start := revision.Add(5 * time.Minute)
|
|
key := models.MaintenanceStartNotificationKey(7, revision, start, "Europe/Berlin", 11, 13)
|
|
assert.Equal(t, key, models.MaintenanceStartNotificationKey(7, revision, start, "Europe/Berlin", 11, 13))
|
|
assert.NotEqual(t, key, models.MaintenanceStartNotificationKey(7, revision.Add(time.Second), start, "Europe/Berlin", 11, 13))
|
|
}
|
|
|
|
func TestMaintenanceStartNotificationKeyDeduplicatesDSTFallbackCivilOccurrence(t *testing.T) {
|
|
revision := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)
|
|
first := time.Date(2026, 10, 25, 0, 30, 0, 0, time.UTC)
|
|
second := first.Add(time.Hour)
|
|
assert.Equal(t, models.MaintenanceStartNotificationKey(7, revision, first, "Europe/Berlin", 11, 13), models.MaintenanceStartNotificationKey(7, revision, second, "Europe/Berlin", 11, 13))
|
|
}
|
|
|
|
func TestMaintenanceNextRunSkipsNonFinalLastDayCandidates(t *testing.T) {
|
|
m := models.Maintenance{Title: "last", Strategy: models.MaintenanceRecurringDayOfMonth, StartTime: "12:00", DaysOfMonth: []string{"lastDay1"}, DurationSec: 60, Active: true, Timezone: "UTC"}
|
|
require.NoError(t, m.Validate())
|
|
next := m.NextRun(time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC))
|
|
require.NotNil(t, next)
|
|
assert.Equal(t, time.Date(2026, 4, 30, 12, 0, 0, 0, time.UTC), *next)
|
|
}
|
|
|
|
func maintenanceTimePtr(value time.Time) *time.Time { return &value }
|