package service_test import ( "context" "testing" "time" "github.com/wotra/wotra/internal/domain" "github.com/wotra/wotra/internal/service" "github.com/wotra/wotra/internal/store" ) func newTestDayServices(t *testing.T) (*service.EntryService, *service.DayService, *service.SettingsService) { 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) settingsStore := store.NewSettingsStore(db) tz, _ := time.LoadLocation("UTC") entrySvc := service.NewEntryService(entryStore, closedDayStore, settingsStore, tz) daySvc := service.NewDayService(entryStore, closedDayStore, settingsStore, tz) settingsSvc := service.NewSettingsService(settingsStore) return entrySvc, daySvc, settingsSvc } func TestCloseDayBasic(t *testing.T) { ctx := context.Background() entrySvc, daySvc, _ := newTestDayServices(t) // Start and stop an entry _, err := entrySvc.Start(ctx, "work") if err != nil { t.Fatal(err) } _, err = entrySvc.Stop(ctx) if err != nil { t.Fatal(err) } today := time.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, _ := newTestDayServices(t) _, err := entrySvc.Start(ctx, "") if err != nil { t.Fatal(err) } today := time.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, _ := newTestDayServices(t) entrySvc.Start(ctx, "") entrySvc.Stop(ctx) today := time.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, _ := newTestDayServices(t) today := time.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) } // Monday-Friday = 40h/5 = 8h = 28800000ms expected if today == time.Now().UTC().Format("2006-01-02") { wd := int(time.Now().UTC().Weekday()) // workdays Mon-Fri (mask=31): weekdays 1-5 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, _ := newTestDayServices(t) entrySvc.Start(ctx, "") entrySvc.Stop(ctx) today := time.Now().UTC().Format("2006-01-02") daySvc.CloseDay(ctx, today) if err := daySvc.ReopenDay(ctx, today); err != nil { t.Fatalf("ReopenDay: %v", err) } // Should be closeable again _, 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)) } }