Files
worktimeBot/internal/domain/totals_test.go
db123 d15ed46066 fix: resolve go vet error and dayStart logic in ComputeDailyTotals
- Fix 'string(d)' int-to-rune conversion in rpg_test.go by using fmt.Sprintf
- Fix ComputeDailyTotals dayStart logic: initial state should be StateWorking
  when dayStart > 0, correctly counting work from dayStart to first out event
- Update README project structure to reflect new domain/handler/repo layout
- Implement missing achievements: rpg_pioneer (on RPG enable) and
  salary_setter (on first rate set)
- Add tryUnlockAchievement helper to Handler
2026-06-25 09:54:22 +03:30

210 lines
5.3 KiB
Go

package domain
import (
"testing"
)
func mkEvent(typ string, ts int64) Event {
return 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([]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 := []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 := []Event{
mkEvent("out", 200),
mkEvent("in", 300),
mkEvent("out", 400),
}
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 := []Event{
mkEvent("in", 100),
mkEvent("out", 200),
mkEvent("in", 300),
}
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 := []Event{
mkEvent("in", 100),
mkEvent("out", 200),
mkEvent("in", 250),
mkEvent("out", 400),
}
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 := []Event{
mkEvent("in", 100),
mkEvent("out", 200),
mkEvent("in", 300),
mkEvent("out", 400),
}
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 := []Event{
mkEvent("in", 100),
mkEvent("out", 200),
mkEvent("out", 250),
mkEvent("in", 300),
mkEvent("out", 400),
}
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 := []Event{
mkEvent("in", 100),
mkEvent("in", 150),
mkEvent("out", 300),
}
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 := []Event{
mkEvent("in", 100),
}
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 := []Event{
mkEvent("out", 200),
}
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 := []Event{
mkEvent("in", 100),
mkEvent("out", 200),
mkEvent("in", 250),
mkEvent("out", 400),
}
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 := []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 := []Event{
mkEvent("in", 100),
mkEvent("out", 200),
mkEvent("in", 300),
mkEvent("out", 400),
mkEvent("in", 500),
mkEvent("out", 600),
}
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 := []Event{
mkEvent("out", 200),
}
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 := []Event{
mkEvent("in", 100),
}
got := ComputeDailyTotals(events, 0, 0, 0)
if got.TotalSeconds != 0 {
t.Errorf("only in no dayEnd: TotalSeconds=%d, want 0", got.TotalSeconds)
}
}