Fix today's habits timezone calculation and include entry data

- Calculate today based on user's timezone instead of always using UTC
- Include habit entry in response if it exists for the current day
- Update GetTodaysHabitsHandler to fetch and return entry information
- Add entry field to TodaysHabitDTO and TodaysHabitResponse
- Update tests to reflect new behavior of including completed habits
- Add test for habits with value entries
This commit is contained in:
2025-11-29 02:09:30 +01:00
parent 9a0637aa4c
commit 90d5628a42
6 changed files with 130 additions and 32 deletions
@@ -9,6 +9,12 @@ import (
"apocapoc-api/internal/shared/utils"
)
type TodaysHabitEntryDTO struct {
ID string
Value *float64
CompletedAt time.Time
}
type TodaysHabitDTO struct {
ID string
Name string
@@ -17,6 +23,7 @@ type TodaysHabitDTO struct {
IsNegative bool
ScheduledDate time.Time
IsCarriedOver bool
Entry *TodaysHabitEntryDTO
}
type GetTodaysHabitsQuery struct {
@@ -66,29 +73,29 @@ func (h *GetTodaysHabitsHandler) Handle(
entries, _ := h.entryRepo.FindByHabitIDAndDateRange(
ctx,
habit.ID,
query.Date.AddDate(0, 0, -30),
query.Date,
query.Date,
)
isCompleted := false
for _, entry := range entries {
if entry.ScheduledDate.Equal(query.Date) {
isCompleted = true
break
var entryDTO *TodaysHabitEntryDTO
if len(entries) > 0 && entries[0].ScheduledDate.Format("2006-01-02") == query.Date.Format("2006-01-02") {
entryDTO = &TodaysHabitEntryDTO{
ID: entries[0].ID,
Value: entries[0].Value,
CompletedAt: entries[0].CompletedAt,
}
}
if !isCompleted {
result = append(result, TodaysHabitDTO{
ID: habit.ID,
Name: habit.Name,
Type: habit.Type,
TargetValue: habit.TargetValue,
IsNegative: habit.IsNegative,
ScheduledDate: query.Date,
IsCarriedOver: !shouldAppear && habit.CarryOver,
})
}
result = append(result, TodaysHabitDTO{
ID: habit.ID,
Name: habit.Name,
Type: habit.Type,
TargetValue: habit.TargetValue,
IsNegative: habit.IsNegative,
ScheduledDate: query.Date,
IsCarriedOver: !shouldAppear && habit.CarryOver,
Entry: entryDTO,
})
}
return result, nil
@@ -107,6 +107,10 @@ func TestGetTodaysHabitsHandler_DailyHabitNoEntries(t *testing.T) {
if results[0].IsCarriedOver {
t.Error("Expected IsCarriedOver to be false")
}
if results[0].Entry != nil {
t.Error("Expected entry to be nil when no entry exists")
}
}
func TestGetTodaysHabitsHandler_DailyHabitAlreadyCompleted(t *testing.T) {
@@ -115,6 +119,7 @@ func TestGetTodaysHabitsHandler_DailyHabitAlreadyCompleted(t *testing.T) {
targetDate := time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC)
entry := entities.NewHabitEntry("habit-1", targetDate, nil)
entry.ID = "entry-1"
habitRepo := &mockHabitRepo{habits: []*entities.Habit{habit}}
entryRepo := &mockEntryRepo{entries: []*entities.HabitEntry{entry}}
@@ -133,8 +138,61 @@ func TestGetTodaysHabitsHandler_DailyHabitAlreadyCompleted(t *testing.T) {
t.Fatalf("Expected no error, got %v", err)
}
if len(results) != 0 {
t.Fatalf("Expected 0 habits (already completed), got %d", len(results))
if len(results) != 1 {
t.Fatalf("Expected 1 habit (with entry), got %d", len(results))
}
if results[0].Entry == nil {
t.Fatal("Expected entry to be present")
}
if results[0].Entry.ID != "entry-1" {
t.Errorf("Expected entry ID entry-1, got %s", results[0].Entry.ID)
}
}
func TestGetTodaysHabitsHandler_HabitWithValueEntry(t *testing.T) {
habit := entities.NewHabit("user-123", "Water", value_objects.HabitTypeValue, value_objects.FrequencyDaily, false, false)
habit.ID = "habit-1"
targetValue := 2000.0
habit.TargetValue = &targetValue
targetDate := time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC)
value := 1500.0
entry := entities.NewHabitEntry("habit-1", targetDate, &value)
entry.ID = "entry-1"
habitRepo := &mockHabitRepo{habits: []*entities.Habit{habit}}
entryRepo := &mockEntryRepo{entries: []*entities.HabitEntry{entry}}
handler := NewGetTodaysHabitsHandler(habitRepo, entryRepo)
query := GetTodaysHabitsQuery{
UserID: "user-123",
Timezone: "UTC",
Date: targetDate,
}
results, err := handler.Handle(context.Background(), query)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(results) != 1 {
t.Fatalf("Expected 1 habit, got %d", len(results))
}
if results[0].Entry == nil {
t.Fatal("Expected entry to be present")
}
if results[0].Entry.Value == nil {
t.Fatal("Expected entry value to be present")
}
if *results[0].Entry.Value != 1500.0 {
t.Errorf("Expected entry value 1500.0, got %f", *results[0].Entry.Value)
}
}