package service_test import ( "context" "testing" "time" "github.com/wotra/wotra/internal/clock" "github.com/wotra/wotra/internal/domain" "github.com/wotra/wotra/internal/service" "github.com/wotra/wotra/internal/store" ) // dayTestAnchor is a fixed Tuesday used as "now" across day service tests. var dayTestAnchor = time.Date(2026, 5, 13, 10, 0, 0, 0, time.UTC) func newTestDayServices(t *testing.T) (*service.EntryService, *service.DayService, *service.SettingsService, *clock.FixedClock) { t.Helper() db, err := store.Open(":memory:") if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) entryStore := store.NewEntryStore(db) closedDayStore := store.NewClosedDayStore(db) closedWeekStore := store.NewClosedWeekStore(db) settingsStore := store.NewSettingsStore(db) syncStore := store.NewSyncStore(db) tz, _ := time.LoadLocation("UTC") clk := clock.Fixed(dayTestAnchor) entrySvc := service.NewEntryService(entryStore, closedDayStore, settingsStore, syncStore, tz, clk) daySvc := service.NewDayService(entryStore, closedDayStore, closedWeekStore, settingsStore, syncStore, tz, clk) settingsSvc := service.NewSettingsService(settingsStore, syncStore, clk) return entrySvc, daySvc, settingsSvc, clk } func TestCloseDayBasic(t *testing.T) { ctx := context.Background() entrySvc, daySvc, _, clk := newTestDayServices(t) _, err := entrySvc.Start(ctx, "work") if err != nil { t.Fatal(err) } _, err = entrySvc.Stop(ctx) if err != nil { t.Fatal(err) } today := clk.Now().UTC().Format("2006-01-02") cd, err := daySvc.CloseDay(ctx, today) if err != nil { t.Fatalf("CloseDay: %v", err) } if cd.Kind != domain.DayKindWork { t.Errorf("expected kind=work, got %s", cd.Kind) } if cd.WorkedMs < 0 { t.Error("expected non-negative worked_ms") } } func TestCloseDayWithRunningEntryFails(t *testing.T) { ctx := context.Background() entrySvc, daySvc, _, clk := newTestDayServices(t) _, err := entrySvc.Start(ctx, "") if err != nil { t.Fatal(err) } today := clk.Now().UTC().Format("2006-01-02") _, err = daySvc.CloseDay(ctx, today) if err == nil { t.Fatal("expected error closing day with running entry") } if err != service.ErrRunningEntryOnDay { t.Fatalf("expected ErrRunningEntryOnDay, got %v", err) } } func TestCloseDayTwiceFails(t *testing.T) { ctx := context.Background() entrySvc, daySvc, _, clk := newTestDayServices(t) entrySvc.Start(ctx, "") entrySvc.Stop(ctx) today := clk.Now().UTC().Format("2006-01-02") daySvc.CloseDay(ctx, today) _, err := daySvc.CloseDay(ctx, today) if err != service.ErrDayAlreadyClosed { t.Fatalf("expected ErrDayAlreadyClosed, got %v", err) } } func TestMarkDayHoliday(t *testing.T) { ctx := context.Background() _, daySvc, _, clk := newTestDayServices(t) today := clk.Now().UTC().Format("2006-01-02") cd, err := daySvc.MarkDay(ctx, today, domain.DayKindHoliday) if err != nil { t.Fatalf("MarkDay: %v", err) } if cd.Kind != domain.DayKindHoliday { t.Errorf("expected kind=holiday, got %s", cd.Kind) } // dayTestAnchor is a Tuesday (weekday 2): Mon-Fri workdays → 40h/5 = 8h = 28800000ms wd := int(clk.Now().UTC().Weekday()) if wd >= 1 && wd <= 5 { if cd.WorkedMs != 8*3600*1000 { t.Errorf("expected 8h worked_ms for holiday on workday, got %d", cd.WorkedMs) } } } func TestReopenDay(t *testing.T) { ctx := context.Background() entrySvc, daySvc, _, clk := newTestDayServices(t) entrySvc.Start(ctx, "") entrySvc.Stop(ctx) today := clk.Now().UTC().Format("2006-01-02") daySvc.CloseDay(ctx, today) if err := daySvc.ReopenDay(ctx, today); err != nil { t.Fatalf("ReopenDay: %v", err) } _, err := daySvc.CloseDay(ctx, today) if err != nil { t.Fatalf("CloseDay after reopen: %v", err) } } func TestSettingsUpsertAndHistory(t *testing.T) { ctx := context.Background() _, _, settingsSvc, _ := newTestDayServices(t) set, err := settingsSvc.Upsert(ctx, service.UpsertSettingsInput{ EffectiveFrom: "2024-01-01", HoursPerWeek: 38.5, WorkdaysMask: 31, Timezone: "Europe/Berlin", }) if err != nil { t.Fatalf("Upsert: %v", err) } if set.HoursPerWeek != 38.5 { t.Errorf("expected 38.5 h/week, got %f", set.HoursPerWeek) } history, err := settingsSvc.History(ctx) if err != nil { t.Fatal(err) } // Seeded default + our new one if len(history) < 2 { t.Fatalf("expected >=2 history entries, got %d", len(history)) } }