test: add dateutil and totals tests for calendar conversions and ComputeDailyTotals
This commit is contained in:
247
internal/bot/dateutil_test.go
Normal file
247
internal/bot/dateutil_test.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGregorianToJDN_RoundTrip(t *testing.T) {
|
||||||
|
cases := []struct{ y, m, d int }{
|
||||||
|
{2024, 1, 1},
|
||||||
|
{2024, 12, 31},
|
||||||
|
{1970, 1, 1},
|
||||||
|
{1, 1, 1},
|
||||||
|
{2024, 2, 29},
|
||||||
|
{1900, 2, 28},
|
||||||
|
{2000, 2, 29},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
jdn := gregorianToJDN(c.y, c.m, c.d)
|
||||||
|
y, m, d := jdnToGregorian(jdn)
|
||||||
|
if y != c.y || m != c.m || d != c.d {
|
||||||
|
t.Errorf("round-trip %04d-%02d-%02d -> JDN %d -> %04d-%02d-%02d", c.y, c.m, c.d, jdn, y, m, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGregorianToJalali_RoundTrip(t *testing.T) {
|
||||||
|
cases := []struct{ gy, gm, gd int }{
|
||||||
|
{2024, 3, 20},
|
||||||
|
{2024, 3, 21},
|
||||||
|
{2024, 9, 22},
|
||||||
|
{2025, 3, 20},
|
||||||
|
{1979, 2, 11},
|
||||||
|
{622, 3, 22},
|
||||||
|
{2024, 12, 31},
|
||||||
|
{2024, 6, 21},
|
||||||
|
{2024, 1, 1},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
jy, jm, jd := gregorianToJalali(c.gy, c.gm, c.gd)
|
||||||
|
if jy == 0 {
|
||||||
|
t.Errorf("gregorianToJalali(%d, %d, %d) returned 0", c.gy, c.gm, c.gd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gy2, gm2, gd2 := jalaliToGregorian(jy, jm, jd)
|
||||||
|
if gy2 != c.gy || gm2 != c.gm || gd2 != c.gd {
|
||||||
|
t.Errorf("round-trip Gregorian %04d-%02d-%02d -> Jalali %04d-%02d-%02d -> Gregorian %04d-%02d-%02d",
|
||||||
|
c.gy, c.gm, c.gd, jy, jm, jd, gy2, gm2, gd2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGregorianToHijri_RoundTrip(t *testing.T) {
|
||||||
|
cases := []struct{ gy, gm, gd int }{
|
||||||
|
{2024, 3, 21},
|
||||||
|
{2024, 1, 1},
|
||||||
|
{2024, 12, 31},
|
||||||
|
{1979, 2, 11},
|
||||||
|
{622, 7, 16},
|
||||||
|
{2024, 7, 7},
|
||||||
|
{2024, 6, 21},
|
||||||
|
{2000, 1, 1},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
hy, hm, hd := gregorianToHijri(c.gy, c.gm, c.gd)
|
||||||
|
if hy == 0 {
|
||||||
|
t.Errorf("gregorianToHijri(%d, %d, %d) returned 0", c.gy, c.gm, c.gd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gy2, gm2, gd2 := hijriToGregorian(hy, hm, hd)
|
||||||
|
diff := (gy2-c.gy)*365 + (gm2-c.gm)*30 + (gd2 - c.gd)
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
if diff > 1 {
|
||||||
|
t.Errorf("round-trip off by %d days: Gregorian %04d-%02d-%02d -> Hijri %04d-%02d-%02d -> Gregorian %04d-%02d-%02d",
|
||||||
|
diff, c.gy, c.gm, c.gd, hy, hm, hd, gy2, gm2, gd2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsLeapYear(t *testing.T) {
|
||||||
|
gregLeaps := map[int]bool{
|
||||||
|
2000: true, 2020: true, 2024: true, 1900: false, 2100: false, 2023: false,
|
||||||
|
}
|
||||||
|
for y, want := range gregLeaps {
|
||||||
|
if got := isGregorianLeap(y); got != want {
|
||||||
|
t.Errorf("isGregorianLeap(%d) = %v, want %v", y, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jalaliLeaps := map[int]bool{
|
||||||
|
1: true, 5: true, 9: true, 13: true, 17: true, 22: true, 26: true, 30: true,
|
||||||
|
2: false, 33: false, 1403: true,
|
||||||
|
}
|
||||||
|
for y, want := range jalaliLeaps {
|
||||||
|
if got := isJalaliLeap(y); got != want {
|
||||||
|
t.Errorf("isJalaliLeap(%d) = %v, want %v", y, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hijriLeaps := map[int]bool{
|
||||||
|
2: true, 5: true, 7: true, 10: true, 13: true, 16: true,
|
||||||
|
18: true, 21: true, 24: true, 27: true, 29: true,
|
||||||
|
1: false, 3: false, 30: false,
|
||||||
|
}
|
||||||
|
for y, want := range hijriLeaps {
|
||||||
|
if got := isHijriLeap(y); got != want {
|
||||||
|
t.Errorf("isHijriLeap(%d) = %v, want %v", y, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonthLengths(t *testing.T) {
|
||||||
|
if got := monthLenGreg(2024, 2); got != 29 {
|
||||||
|
t.Errorf("monthLenGreg(2024, 2) = %d, want 29", got)
|
||||||
|
}
|
||||||
|
if got := monthLenGreg(2023, 2); got != 28 {
|
||||||
|
t.Errorf("monthLenGreg(2023, 2) = %d, want 28", got)
|
||||||
|
}
|
||||||
|
if got := monthLenGreg(2024, 1); got != 31 {
|
||||||
|
t.Errorf("monthLenGreg(2024, 1) = %d, want 31", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := monthLenJalali(1, 12); got != 30 {
|
||||||
|
t.Errorf("monthLenJalali(1, 12) = %d, want 30 (leap)", got)
|
||||||
|
}
|
||||||
|
if got := monthLenJalali(2, 12); got != 29 {
|
||||||
|
t.Errorf("monthLenJalali(2, 12) = %d, want 29 (non-leap)", got)
|
||||||
|
}
|
||||||
|
if got := monthLenJalali(1403, 1); got != 31 {
|
||||||
|
t.Errorf("monthLenJalali(1403, 1) = %d, want 31", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := monthLenHijri(1, 1); got != 30 {
|
||||||
|
t.Errorf("monthLenHijri(1, 1) = %d, want 30 (odd month)", got)
|
||||||
|
}
|
||||||
|
if got := monthLenHijri(1, 2); got != 29 {
|
||||||
|
t.Errorf("monthLenHijri(1, 2) = %d, want 29 (even non-leap)", got)
|
||||||
|
}
|
||||||
|
if got := monthLenHijri(2, 12); got != 30 {
|
||||||
|
t.Errorf("monthLenHijri(2, 12) = %d, want 30 (leap year, month 12)", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGregorianDateKey(t *testing.T) {
|
||||||
|
y, m, d := parseGregorianDateKey("2024-03-21")
|
||||||
|
if y != 2024 || m != 3 || d != 21 {
|
||||||
|
t.Errorf("parseGregorianDateKey got %d-%d-%d, want 2024-3-21", y, m, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatDateForCalendar(t *testing.T) {
|
||||||
|
if got := formatDateForCalendar("2024-03-21", "gregorian"); got != "2024-03-21" {
|
||||||
|
t.Errorf("formatDateForCalendar gregorian: got %s", got)
|
||||||
|
}
|
||||||
|
if got := formatDateForCalendar("2024-03-21", "jalali"); got != "1403-01-02" {
|
||||||
|
t.Errorf("formatDateForCalendar jalali: got %s, want 1403-01-02", got)
|
||||||
|
}
|
||||||
|
if got := formatDateForCalendar("2024-03-21", "hijri"); got != "1445-09-11" {
|
||||||
|
t.Errorf("formatDateForCalendar hijri: got %s, want 1445-09-11", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserDateToGregorian(t *testing.T) {
|
||||||
|
if got := userDateToGregorian("1403-01-02", "jalali"); got != "2024-03-21" {
|
||||||
|
t.Errorf("userDateToGregorian jalali: got %s, want 2024-03-21", got)
|
||||||
|
}
|
||||||
|
if got := userDateToGregorian("1445-09-11", "hijri"); got != "2024-03-21" {
|
||||||
|
t.Errorf("userDateToGregorian hijri: got %s, want 2024-03-21", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonthGregorianRange(t *testing.T) {
|
||||||
|
start, end := monthGregorianRange("gregorian", 2024, 1)
|
||||||
|
if start != "2024-01-01" || end != "2024-01-31" {
|
||||||
|
t.Errorf("gregorian range: got %s - %s", start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
start, end = monthGregorianRange("jalali", 1403, 1)
|
||||||
|
if start != "2024-03-20" || end != "2024-04-19" {
|
||||||
|
t.Errorf("jalali range: got %s - %s", start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
start, end = monthGregorianRange("hijri", 1445, 9)
|
||||||
|
if start != "2024-03-11" || end != "2024-04-09" {
|
||||||
|
t.Errorf("hijri range: got %s - %s", start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildCalendarMonth(t *testing.T) {
|
||||||
|
cm := buildCalendarMonth("gregorian", 2024, 1)
|
||||||
|
if cm.title != "January 2024" {
|
||||||
|
t.Errorf("title: got %q", cm.title)
|
||||||
|
}
|
||||||
|
if len(cm.weekDays) != 7 {
|
||||||
|
t.Errorf("weekDays count: %d", len(cm.weekDays))
|
||||||
|
}
|
||||||
|
if len(cm.days) < 28 {
|
||||||
|
t.Errorf("too few days: %d", len(cm.days))
|
||||||
|
}
|
||||||
|
|
||||||
|
cm2 := buildCalendarMonth("jalali", 1403, 1)
|
||||||
|
if cm2.title != "Farvardin 1403" {
|
||||||
|
t.Errorf("jalali title: got %q", cm2.title)
|
||||||
|
}
|
||||||
|
if len(cm2.days) < 31 {
|
||||||
|
t.Errorf("too few jalali days: %d", len(cm2.days))
|
||||||
|
}
|
||||||
|
|
||||||
|
cm3 := buildCalendarMonth("hijri", 1445, 9)
|
||||||
|
if cm3.title != "Ramadan 1445" {
|
||||||
|
t.Errorf("hijri title: got %q", cm3.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadLocation(t *testing.T) {
|
||||||
|
loc := loadLocation("Asia/Tehran")
|
||||||
|
if loc == nil {
|
||||||
|
t.Fatal("loadLocation returned nil")
|
||||||
|
}
|
||||||
|
if loc.String() != "Asia/Tehran" {
|
||||||
|
t.Errorf("unexpected location: %s", loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
loc = loadLocation("Invalid/Timezone")
|
||||||
|
if loc.String() != "UTC" {
|
||||||
|
t.Errorf("expected UTC fallback, got %s", loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatDuration(t *testing.T) {
|
||||||
|
if got := formatDuration(3600); got != "1h 0m" {
|
||||||
|
t.Errorf("formatDuration(3600) = %q, want %q", got, "1h 0m")
|
||||||
|
}
|
||||||
|
if got := formatDuration(3660); got != "1h 1m" {
|
||||||
|
t.Errorf("formatDuration(3660) = %q, want %q", got, "1h 1m")
|
||||||
|
}
|
||||||
|
if got := formatDuration(59); got != "0m" {
|
||||||
|
t.Errorf("formatDuration(59) = %q, want %q", got, "0m")
|
||||||
|
}
|
||||||
|
if got := formatDuration(1800); got != "30m" {
|
||||||
|
t.Errorf("formatDuration(1800) = %q, want %q", got, "30m")
|
||||||
|
}
|
||||||
|
if got := formatDuration(0); got != "0m" {
|
||||||
|
t.Errorf("formatDuration(0) = %q, want %q", got, "0m")
|
||||||
|
}
|
||||||
|
}
|
||||||
228
internal/bot/totals_test.go
Normal file
228
internal/bot/totals_test.go
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"worktimeBot/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mkEvent(typ string, ts int64) db.Event {
|
||||||
|
return db.Event{EventType: typ, OccurredAt: ts}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_Empty(t *testing.T) {
|
||||||
|
got := ComputeDailyTotals(nil, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 0 || got.BreakSeconds != 0 {
|
||||||
|
t.Errorf("empty input: got %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_NoEvents(t *testing.T) {
|
||||||
|
got := ComputeDailyTotals([]db.Event{}, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 0 || got.BreakSeconds != 0 {
|
||||||
|
t.Errorf("no events: got %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_SingleInOut(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
}
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 100 {
|
||||||
|
t.Errorf("single in/out: TotalSeconds=%d, want 100", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
if got.BreakSeconds != 0 {
|
||||||
|
t.Errorf("single in/out: BreakSeconds=%d, want 0", got.BreakSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_MissingFirstIn(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 300),
|
||||||
|
mkEvent("out", 400),
|
||||||
|
}
|
||||||
|
// synthetic "in" at dayStart=100 => in(100), out(200)=100s work, break(200-300), in(300), out(400)=100s work
|
||||||
|
got := ComputeDailyTotals(events, 0, 100, 500)
|
||||||
|
if got.TotalSeconds != 200 {
|
||||||
|
t.Errorf("missing first in: TotalSeconds=%d, want 200", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_MissingLastOut(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 300),
|
||||||
|
}
|
||||||
|
// synthetic "out" at dayEnd=500 => 100s + 200s
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 500)
|
||||||
|
if got.TotalSeconds != 300 {
|
||||||
|
t.Errorf("missing last out: TotalSeconds=%d, want 300", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_BreakUnderThreshold(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 250),
|
||||||
|
mkEvent("out", 400),
|
||||||
|
}
|
||||||
|
// break = 50s, threshold = 60s => break absorbed into work
|
||||||
|
// work: 100-200(100s), then 200-250 absorbed(50s), 250-400(150s) = 300s
|
||||||
|
got := ComputeDailyTotals(events, 60, 0, 0)
|
||||||
|
if got.TotalSeconds != 300 {
|
||||||
|
t.Errorf("break under threshold: TotalSeconds=%d, want 300", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
if got.BreakSeconds != 0 {
|
||||||
|
t.Errorf("break under threshold: BreakSeconds=%d, want 0", got.BreakSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_BreakOverThreshold(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 300),
|
||||||
|
mkEvent("out", 400),
|
||||||
|
}
|
||||||
|
// break = 100s, threshold = 60s => counted as break
|
||||||
|
got := ComputeDailyTotals(events, 60, 0, 0)
|
||||||
|
if got.TotalSeconds != 200 {
|
||||||
|
t.Errorf("break over threshold: TotalSeconds=%d, want 200", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
if got.BreakSeconds != 100 {
|
||||||
|
t.Errorf("break over threshold: BreakSeconds=%d, want 100", got.BreakSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_ConsecutiveOuts(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("out", 250),
|
||||||
|
mkEvent("in", 300),
|
||||||
|
mkEvent("out", 400),
|
||||||
|
}
|
||||||
|
// in(100)..out(200)=100s work, state=OnBreak breakStart=200
|
||||||
|
// out(250) while OnBreak => reset breakStart=250
|
||||||
|
// in(300) => state=Working, workStart=300
|
||||||
|
// out(400) => work=100s
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 200 {
|
||||||
|
t.Errorf("consecutive outs: TotalSeconds=%d, want 200", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_ConsecutiveIns(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("in", 150),
|
||||||
|
mkEvent("out", 300),
|
||||||
|
}
|
||||||
|
// second "in" while already working is ignored
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 200 {
|
||||||
|
t.Errorf("consecutive ins: TotalSeconds=%d, want 200", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_SingleInEndOfDay(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
}
|
||||||
|
// missing out => synthetic out at dayEnd
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 500)
|
||||||
|
if got.TotalSeconds != 400 {
|
||||||
|
t.Errorf("single in with dayEnd: TotalSeconds=%d, want 400", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_SingleOutStartOfDay(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("out", 200),
|
||||||
|
}
|
||||||
|
// missing in => synthetic in at dayStart
|
||||||
|
got := ComputeDailyTotals(events, 0, 100, 500)
|
||||||
|
if got.TotalSeconds != 100 {
|
||||||
|
t.Errorf("single out with dayStart: TotalSeconds=%d, want 100", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_ZeroThreshold(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 250),
|
||||||
|
mkEvent("out", 400),
|
||||||
|
}
|
||||||
|
// threshold = 0, any break is counted
|
||||||
|
// work: 100-200(100s) + 250-400(150s) = 250s, break: 200-250(50s)
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 250 {
|
||||||
|
t.Errorf("zero threshold: TotalSeconds=%d, want 250", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
if got.BreakSeconds != 50 {
|
||||||
|
t.Errorf("zero threshold: BreakSeconds=%d, want 50", got.BreakSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_LargeBreak(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 10000),
|
||||||
|
mkEvent("out", 10100),
|
||||||
|
}
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 200 {
|
||||||
|
t.Errorf("large break: TotalSeconds=%d, want 200", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
if got.BreakSeconds != 9800 {
|
||||||
|
t.Errorf("large break: BreakSeconds=%d, want 9800", got.BreakSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_MultipleSessions(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
mkEvent("out", 200),
|
||||||
|
mkEvent("in", 300),
|
||||||
|
mkEvent("out", 400),
|
||||||
|
mkEvent("in", 500),
|
||||||
|
mkEvent("out", 600),
|
||||||
|
}
|
||||||
|
// 3 work sessions: 100+100+100 = 300s
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 300 {
|
||||||
|
t.Errorf("multiple sessions: TotalSeconds=%d, want 300", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
if got.BreakSeconds != 200 {
|
||||||
|
t.Errorf("multiple sessions: BreakSeconds=%d, want 200", got.BreakSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_OnlyOut(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("out", 200),
|
||||||
|
}
|
||||||
|
// no dayStart => no synthetic in => idle -> out triggers OnBreak
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 0 {
|
||||||
|
t.Errorf("only out no dayStart: TotalSeconds=%d, want 0", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeDailyTotals_OnlyIn(t *testing.T) {
|
||||||
|
events := []db.Event{
|
||||||
|
mkEvent("in", 100),
|
||||||
|
}
|
||||||
|
// no dayEnd => no synthetic out => still working at end
|
||||||
|
got := ComputeDailyTotals(events, 0, 0, 0)
|
||||||
|
if got.TotalSeconds != 0 {
|
||||||
|
t.Errorf("only in no dayEnd: TotalSeconds=%d, want 0", got.TotalSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user