package api import ( "testing" "time" "gardomatic.kleiax.de/internal/storage" ) type templateTestModel struct { items map[int]storage.SpeciesTaskTemplate nextID int } func (m *templateTestModel) Insert(value storage.SpeciesTaskTemplate) (storage.SpeciesTaskTemplate, error) { m.nextID++ value.ID, value.Version = m.nextID, 1 m.items[value.ID] = value return value, nil } func (m *templateTestModel) Get(gardenID, id int) (storage.SpeciesTaskTemplate, error) { value, ok := m.items[id] if !ok { return storage.SpeciesTaskTemplate{}, storage.ErrRecordNotFound } return value, nil } func (m *templateTestModel) GetAllForSpecies(gardenID, speciesID int) ([]storage.SpeciesTaskTemplate, error) { result := []storage.SpeciesTaskTemplate{} for _, value := range m.items { if value.SpeciesID == speciesID { result = append(result, value) } } return result, nil } func (m *templateTestModel) Update(gardenID int, value storage.SpeciesTaskTemplate) (storage.SpeciesTaskTemplate, error) { value.Version++ m.items[value.ID] = value return value, nil } func (m *templateTestModel) Delete(gardenID, id int) error { delete(m.items, id); return nil } func TestGenerateTasksIsIdempotentAndHandlesWrapAround(t *testing.T) { app, _, _ := newGardenTestApplication() speciesID, monthFrom, dayFrom := 2, 11, 1 app.models.Plants = &plantTestModel{items: map[int]storage.Plant{5: {ID: 5, GardenID: 3, SpeciesID: &speciesID, Name: "Rose", Status: "active"}}} app.models.SpeciesTaskTemplates = &templateTestModel{items: map[int]storage.SpeciesTaskTemplate{7: {ID: 7, SpeciesID: speciesID, Title: "Winterschutz prüfen", TriggerType: storage.TaskTriggerMonthOfYear, MonthFrom: &monthFrom, DayFrom: &dayFrom, Duration: 3, DurationUnit: storage.TaskDurationMonth, Recurrence: storage.TaskRecurrenceWeekly, RecurrenceInterval: 2, Active: true}}} tasks := &taskTestModel{items: map[int]storage.Task{}, nextID: 10} app.models.Tasks = tasks now := time.Date(2026, 9, 1, 12, 0, 0, 0, time.UTC) if err := app.generateTasks(3, 12, now); err != nil { t.Fatal(err) } firstCount := len(tasks.items) if firstCount != 1 { t.Fatalf("generated count=%d", firstCount) } if err := app.generateTasks(3, 12, now); err != nil { t.Fatal(err) } if len(tasks.items) != firstCount { t.Fatalf("second generation count=%d want=%d", len(tasks.items), firstCount) } for _, task := range tasks.items { if task.Recurrence != storage.TaskRecurrenceWeekly || task.RecurrenceInterval != 2 { t.Errorf("recurrence not copied to generated task: %+v", task) } if task.DueAtStart.Before(time.Date(2026, 11, 1, 0, 0, 0, 0, time.UTC)) || task.DueAtEnd.After(endOfDay(time.Date(2027, 2, 1, 0, 0, 0, 0, time.UTC))) { t.Errorf("window outside wrap range: %+v", task) } } }