diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-03 22:36:46 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-03 22:36:46 +0200 |
| commit | c3c347d6faed97d9cc02bf326e2a74786b0bde99 (patch) | |
| tree | 38a4ad0d4ccd964d5e59d42e6916e540fa92c6d4 /internal/worktime/entries_test.go | |
| parent | b2cf337e0a4182dba6d0cbe18c0606e607d143c7 (diff) | |
Task 354: add worktime entry operations
Diffstat (limited to 'internal/worktime/entries_test.go')
| -rw-r--r-- | internal/worktime/entries_test.go | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/internal/worktime/entries_test.go b/internal/worktime/entries_test.go new file mode 100644 index 0000000..1327a1a --- /dev/null +++ b/internal/worktime/entries_test.go @@ -0,0 +1,209 @@ +package worktime + +import ( + "testing" + "time" +) + +func TestLoginLogoutValidation(t *testing.T) { + dbDir := t.TempDir() + host := "host-a" + + loginEntry, err := Login(dbDir, host, "work", time.Unix(100, 0), "start") + if err != nil { + t.Fatalf("Login() error = %v", err) + } + if loginEntry.Action != "login" || loginEntry.What != "work" { + t.Fatalf("unexpected login entry: %+v", loginEntry) + } + + if _, err := Login(dbDir, host, "work", time.Unix(110, 0), "start again"); err == nil { + t.Fatal("Login() error = nil, want already logged in error") + } + + logoutEntry, err := Logout(dbDir, host, "work", time.Unix(120, 0), "stop") + if err != nil { + t.Fatalf("Logout() error = %v", err) + } + if logoutEntry.Action != "logout" || logoutEntry.What != "work" { + t.Fatalf("unexpected logout entry: %+v", logoutEntry) + } + + if _, err := Logout(dbDir, host, "work", time.Unix(130, 0), "stop again"); err == nil { + t.Fatal("Logout() error = nil, want not logged in error") + } +} + +func TestLoginValidationIsCategoryScoped(t *testing.T) { + dbDir := t.TempDir() + host := "host-a" + + if _, err := Login(dbDir, host, "work", time.Unix(100, 0), ""); err != nil { + t.Fatalf("Login(work) error = %v", err) + } + if _, err := Login(dbDir, host, "lunch", time.Unix(110, 0), ""); err != nil { + t.Fatalf("Login(lunch) error = %v", err) + } +} + +func TestAddSubAndUseBuffer(t *testing.T) { + dbDir := t.TempDir() + host := "host-a" + + added, err := Add(dbDir, host, "", 30*time.Minute, time.Unix(100, 0), "manual add") + if err != nil { + t.Fatalf("Add() error = %v", err) + } + if added.Action != "add" || added.What != "work" || added.Value != 1800 { + t.Fatalf("unexpected Add() entry: %+v", added) + } + + subbed, err := Sub(dbDir, host, "work", 15*time.Minute, time.Unix(200, 0), "manual sub") + if err != nil { + t.Fatalf("Sub() error = %v", err) + } + if subbed.Value != -900 { + t.Fatalf("Sub() value = %d, want -900", subbed.Value) + } + + bufferEntries, err := UseBuffer(dbDir, host, 10*time.Minute, time.Unix(300, 0), "buffer transfer") + if err != nil { + t.Fatalf("UseBuffer() error = %v", err) + } + if len(bufferEntries) != 2 { + t.Fatalf("UseBuffer() len = %d, want 2", len(bufferEntries)) + } + if bufferEntries[0].What != "selfdevelopment" || bufferEntries[0].Value != -600 { + t.Fatalf("unexpected buffer remove entry: %+v", bufferEntries[0]) + } + if bufferEntries[1].What != "work" || bufferEntries[1].Value != 600 { + t.Fatalf("unexpected buffer add entry: %+v", bufferEntries[1]) + } + + db, err := LoadHost(dbDir, host) + if err != nil { + t.Fatalf("LoadHost() error = %v", err) + } + + entries := db.Entries[host] + if len(entries) != 4 { + t.Fatalf("entries len = %d, want 4", len(entries)) + } + if entries[0].Epoch != 100 || entries[1].Epoch != 200 || entries[2].Epoch != 300 || entries[3].Epoch != 300 { + t.Fatalf("entries not sorted by epoch: %+v", entries) + } +} + +func TestDurationValidation(t *testing.T) { + dbDir := t.TempDir() + host := "host-a" + + if _, err := Add(dbDir, host, "work", 0, time.Unix(100, 0), ""); err == nil { + t.Fatal("Add() accepted zero duration") + } + if _, err := Add(dbDir, host, "work", -time.Minute, time.Unix(100, 0), ""); err == nil { + t.Fatal("Add() accepted negative duration") + } + if _, err := Sub(dbDir, host, "work", 0, time.Unix(100, 0), ""); err == nil { + t.Fatal("Sub() accepted zero duration") + } + if _, err := UseBuffer(dbDir, host, 0, time.Unix(100, 0), ""); err == nil { + t.Fatal("UseBuffer() accepted zero duration") + } +} + +func TestEditAndDeleteEntry(t *testing.T) { + dbDir := t.TempDir() + host := "host-a" + + if _, err := Add(dbDir, host, "work", 5*time.Minute, time.Unix(100, 0), "first"); err != nil { + t.Fatalf("Add(first) error = %v", err) + } + if _, err := Add(dbDir, host, "work", 6*time.Minute, time.Unix(200, 0), "second"); err != nil { + t.Fatalf("Add(second) error = %v", err) + } + + edited, err := EditEntry(dbDir, host, 0, Entry{ + Action: "ADD", + What: "off", + Epoch: 100, + Value: 120, + Descr: "updated", + }) + if err != nil { + t.Fatalf("EditEntry() error = %v", err) + } + if edited.Action != "add" || edited.What != "off" || edited.Source != host || edited.Value != 120 { + t.Fatalf("unexpected edited entry: %+v", edited) + } + + dbAfterEdit, err := LoadHost(dbDir, host) + if err != nil { + t.Fatalf("LoadHost() after edit error = %v", err) + } + if dbAfterEdit.Entries[host][0].What != "off" { + t.Fatalf("entry was not edited: %+v", dbAfterEdit.Entries[host][0]) + } + + removed, err := DeleteEntry(dbDir, host, 1) + if err != nil { + t.Fatalf("DeleteEntry() error = %v", err) + } + if removed.Descr != "second" { + t.Fatalf("unexpected removed entry: %+v", removed) + } + + dbAfterDelete, err := LoadHost(dbDir, host) + if err != nil { + t.Fatalf("LoadHost() after delete error = %v", err) + } + if len(dbAfterDelete.Entries[host]) != 1 { + t.Fatalf("entries len after delete = %d, want 1", len(dbAfterDelete.Entries[host])) + } + + if _, err := EditEntry(dbDir, host, 5, Entry{Action: "add", Epoch: 1}); err == nil { + t.Fatal("EditEntry() accepted out-of-range index") + } + if _, err := DeleteEntry(dbDir, host, 5); err == nil { + t.Fatal("DeleteEntry() accepted out-of-range index") + } +} + +func TestEditEntryValidation(t *testing.T) { + dbDir := t.TempDir() + host := "host-a" + + if _, err := Add(dbDir, host, "work", time.Minute, time.Unix(100, 0), "seed"); err != nil { + t.Fatalf("Add(seed) error = %v", err) + } + + if _, err := EditEntry(dbDir, host, 0, Entry{Action: "bad", Epoch: 1}); err == nil { + t.Fatal("EditEntry() accepted unsupported action") + } + if _, err := EditEntry(dbDir, host, 0, Entry{Action: "add", Epoch: 0}); err == nil { + t.Fatal("EditEntry() accepted non-positive epoch") + } + if _, err := EditEntry(dbDir, host, 0, Entry{Action: "add", Epoch: 1, Source: "other-host"}); err == nil { + t.Fatal("EditEntry() accepted mismatched source") + } +} + +func TestGlobalLoginValidationAcrossHosts(t *testing.T) { + dbDir := t.TempDir() + + if _, err := Login(dbDir, "host-a", "work", time.Unix(100, 0), ""); err != nil { + t.Fatalf("Login(host-a) error = %v", err) + } + + if _, err := Login(dbDir, "host-b", "work", time.Unix(110, 0), ""); err == nil { + t.Fatal("Login(host-b) should fail while work is already logged in") + } + + if _, err := Logout(dbDir, "host-b", "work", time.Unix(120, 0), ""); err != nil { + t.Fatalf("Logout(host-b) error = %v", err) + } + + if _, err := Logout(dbDir, "host-a", "work", time.Unix(130, 0), ""); err == nil { + t.Fatal("Logout(host-a) should fail because work is already logged out") + } +} |
