229 lines
6.3 KiB
Go
229 lines
6.3 KiB
Go
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)
|
|
}
|
|
}
|