package chore import ( "encoding/json" "testing" "time" chModel "donetick.com/core/internal/chore/model" ) func TestScheduleNextDueDateBasic(t *testing.T) { choreTime := time.Now() freqencyMetadataBytes := `{"time":"2024-07-07T14:30:00-04:00"}` testTable := []struct { chore *chModel.Chore expected time.Time }{ { chore: &chModel.Chore{ FrequencyType: chModel.FrequancyTypeDaily, NextDueDate: &choreTime, FrequencyMetadata: &freqencyMetadataBytes, }, expected: choreTime.AddDate(0, 0, 1), }, { chore: &chModel.Chore{ FrequencyType: chModel.FrequancyTypeWeekly, NextDueDate: &choreTime, FrequencyMetadata: &freqencyMetadataBytes, }, expected: choreTime.AddDate(0, 0, 7), }, { chore: &chModel.Chore{ FrequencyType: chModel.FrequancyTypeMonthly, NextDueDate: &choreTime, FrequencyMetadata: &freqencyMetadataBytes, }, expected: choreTime.AddDate(0, 1, 0), }, { chore: &chModel.Chore{ FrequencyType: chModel.FrequancyTypeYearly, NextDueDate: &choreTime, FrequencyMetadata: &freqencyMetadataBytes, }, expected: choreTime.AddDate(1, 0, 0), }, // } for _, tt := range testTable { t.Run(string(tt.chore.FrequencyType), func(t *testing.T) { actual, err := scheduleNextDueDate(tt.chore, choreTime) if err != nil { t.Errorf("Error: %v", err) } if actual != nil && actual.UTC().Format(time.RFC3339) != tt.expected.UTC().Format(time.RFC3339) { t.Errorf("Expected: %v, Actual: %v", tt.expected, actual) } }) } } func TestScheduleNextDueDateDayOfTheWeek(t *testing.T) { choreTime := time.Now() Monday := "monday" Wednesday := "wednesday" timeOfChore := "2024-07-07T16:30:00-04:00" getExpectedTime := func(choreTime time.Time, timeOfChore string) time.Time { t, err := time.Parse(time.RFC3339, timeOfChore) if err != nil { return time.Time{} } return time.Date(choreTime.Year(), choreTime.Month(), choreTime.Day(), t.Hour(), t.Minute(), 0, 0, t.Location()) } nextSaturday := choreTime.AddDate(0, 0, 1) for nextSaturday.Weekday() != time.Saturday { nextSaturday = nextSaturday.AddDate(0, 0, 1) } nextMonday := choreTime.AddDate(0, 0, 1) for nextMonday.Weekday() != time.Monday { nextMonday = nextMonday.AddDate(0, 0, 1) } nextTuesday := choreTime.AddDate(0, 0, 1) for nextTuesday.Weekday() != time.Tuesday { nextTuesday = nextTuesday.AddDate(0, 0, 1) } nextWednesday := choreTime.AddDate(0, 0, 1) for nextWednesday.Weekday() != time.Wednesday { nextWednesday = nextWednesday.AddDate(0, 0, 1) } nextThursday := choreTime.AddDate(0, 0, 1) for nextThursday.Weekday() != time.Thursday { nextThursday = nextThursday.AddDate(0, 0, 1) } testTable := []struct { chore *chModel.Chore frequencyMetadata *chModel.FrequencyMetadata expected time.Time }{ { chore: &chModel.Chore{ FrequencyType: chModel.FrequancyTypeDayOfTheWeek, NextDueDate: &nextSaturday, }, frequencyMetadata: &chModel.FrequencyMetadata{ Time: timeOfChore, Days: []*string{&Monday, &Wednesday}, }, expected: getExpectedTime(nextMonday, timeOfChore), }, { chore: &chModel.Chore{ FrequencyType: chModel.FrequancyTypeDayOfTheWeek, NextDueDate: &nextMonday, }, frequencyMetadata: &chModel.FrequencyMetadata{ Time: timeOfChore, Days: []*string{&Monday, &Wednesday}, }, expected: getExpectedTime(nextWednesday, timeOfChore), }, } for _, tt := range testTable { t.Run(string(tt.chore.FrequencyType), func(t *testing.T) { bytesFrequencyMetadata, err := json.Marshal(tt.frequencyMetadata) stringFrequencyMetadata := string(bytesFrequencyMetadata) if err != nil { t.Errorf("Error: %v", err) } tt.chore.FrequencyMetadata = &stringFrequencyMetadata actual, err := scheduleNextDueDate(tt.chore, choreTime) if err != nil { t.Errorf("Error: %v", err) } if actual != nil && actual.UTC().Format(time.RFC3339) != tt.expected.UTC().Format(time.RFC3339) { t.Errorf("Expected: %v, Actual: %v", tt.expected, actual) } }) } } func TestScheduleAdaptiveNextDueDate(t *testing.T) { getTimeFromDate := func(timeOfChore string) *time.Time { t, err := time.Parse(time.RFC3339, timeOfChore) if err != nil { return nil } return &t } testTable := []struct { description string history []*chModel.ChoreHistory chore *chModel.Chore expected *time.Time completeDate *time.Time }{ { description: "Every Two days", chore: &chModel.Chore{ NextDueDate: getTimeFromDate("2024-07-13T01:30:00-00:00"), }, history: []*chModel.ChoreHistory{ { CompletedAt: getTimeFromDate("2024-07-11T01:30:00-00:00"), }, // { // CompletedAt: getTimeFromDate("2024-07-09T01:30:00-00:00"), // }, // { // CompletedAt: getTimeFromDate("2024-07-07T01:30:00-00:00"), // }, }, expected: getTimeFromDate("2024-07-15T01:30:00-00:00"), }, { description: "Every 8 days", chore: &chModel.Chore{ NextDueDate: getTimeFromDate("2024-07-13T01:30:00-00:00"), }, history: []*chModel.ChoreHistory{ { CompletedAt: getTimeFromDate("2024-07-05T01:30:00-00:00"), }, { CompletedAt: getTimeFromDate("2024-06-27T01:30:00-00:00"), }, }, expected: getTimeFromDate("2024-07-21T01:30:00-00:00"), }, { description: "40 days with limit Data", chore: &chModel.Chore{ NextDueDate: getTimeFromDate("2024-07-13T01:30:00-00:00"), }, history: []*chModel.ChoreHistory{ {CompletedAt: getTimeFromDate("2024-06-03T01:30:00-00:00")}, }, expected: getTimeFromDate("2024-08-22T01:30:00-00:00"), }, } for _, tt := range testTable { t.Run(tt.description, func(t *testing.T) { expectedNextDueDate := tt.expected actualNextDueDate, err := scheduleAdaptiveNextDueDate(tt.chore, *tt.chore.NextDueDate, tt.history) if err != nil { t.Errorf("Error: %v", err) } if actualNextDueDate == nil || !actualNextDueDate.Equal(*expectedNextDueDate) { t.Errorf("Expected: %v, Actual: %v", expectedNextDueDate, actualNextDueDate) } }) } }