diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-03 23:43:48 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-03 23:43:48 +0200 |
| commit | 7e6b2e4c2ba957899b12dac7e0ea9f7c29a9a7b3 (patch) | |
| tree | 40ddb216b080bd0da08502bd855c25d45f9dc4cd | |
| parent | 293406c3bd2acc85490da11afabaf6733babd5e4 (diff) | |
worktime: use sentinel login state errors
Replace string matching in timer sync with errors.Is and add regression tests.
| -rw-r--r-- | internal/cli/timer.go | 2 | ||||
| -rw-r--r-- | internal/cli/timer_test.go | 27 | ||||
| -rw-r--r-- | internal/worktime/entries.go | 11 | ||||
| -rw-r--r-- | internal/worktime/entries_test.go | 5 |
4 files changed, 42 insertions, 3 deletions
diff --git a/internal/cli/timer.go b/internal/cli/timer.go index 78d6d8d..46292ad 100644 --- a/internal/cli/timer.go +++ b/internal/cli/timer.go @@ -242,7 +242,7 @@ func syncWorktimeWithTimer(start bool) error { } // Avoid failing timer commands on no-op state sync mismatches. - if strings.Contains(err.Error(), "already logged in") || strings.Contains(err.Error(), "not logged in") { + if errors.Is(err, worktime.ErrAlreadyLoggedIn) || errors.Is(err, worktime.ErrNotLoggedIn) { return nil } diff --git a/internal/cli/timer_test.go b/internal/cli/timer_test.go index 25f9618..32b9b8b 100644 --- a/internal/cli/timer_test.go +++ b/internal/cli/timer_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" "testing" + "time" timrTimer "codeberg.org/snonux/timr/internal/timer" "codeberg.org/snonux/timr/internal/worktime" @@ -113,6 +114,32 @@ func TestTimerAutoWorktimeSync(t *testing.T) { } } +func TestTimerAutoWorktimeSyncIgnoresAlreadyLoggedIn(t *testing.T) { + setupTimerState(t) + + dbDir := t.TempDir() + if _, err := worktime.Login(dbDir, "host-auto", "work", time.Unix(100, 0), "seed"); err != nil { + t.Fatalf("seed Login() error = %v", err) + } + + cfgPath := writeWorkConfigWithAuto(t, dbDir, "host-auto", true) + out, err := runRootCommand("--config", cfgPath, "timer", "start") + if err != nil { + t.Fatalf("timer start error = %v (output: %q)", err, out) + } +} + +func TestTimerAutoWorktimeSyncIgnoresNotLoggedInOnStop(t *testing.T) { + setupTimerState(t) + + dbDir := t.TempDir() + cfgPath := writeWorkConfigWithAuto(t, dbDir, "host-auto", true) + out, err := runRootCommand("--config", cfgPath, "timer", "stop") + if err != nil { + t.Fatalf("timer stop error = %v (output: %q)", err, out) + } +} + func setupTimerState(t *testing.T) { t.Helper() diff --git a/internal/worktime/entries.go b/internal/worktime/entries.go index f9226ce..b9a7e40 100644 --- a/internal/worktime/entries.go +++ b/internal/worktime/entries.go @@ -13,6 +13,13 @@ const ( actionAdd = "add" ) +var ( + // ErrAlreadyLoggedIn indicates that a category already has an open login entry. + ErrAlreadyLoggedIn = errors.New("already logged in") + // ErrNotLoggedIn indicates that a category has no active login entry. + ErrNotLoggedIn = errors.New("not logged in") +) + // Login creates a login entry after validating the category is not already logged in. func Login(dbDir, hostname, category string, at time.Time, descr string) (Entry, error) { host, err := normalizeHostname(hostname) @@ -26,7 +33,7 @@ func Login(dbDir, hostname, category string, at time.Time, descr string) (Entry, return Entry{}, err } if loggedIn { - return Entry{}, fmt.Errorf("already logged in for %q", cat) + return Entry{}, fmt.Errorf("%w for %q", ErrAlreadyLoggedIn, cat) } entry := newEntry(actionLogin, host, cat, at, 0, descr) @@ -46,7 +53,7 @@ func Logout(dbDir, hostname, category string, at time.Time, descr string) (Entry return Entry{}, err } if !loggedIn { - return Entry{}, fmt.Errorf("not logged in for %q", cat) + return Entry{}, fmt.Errorf("%w for %q", ErrNotLoggedIn, cat) } entry := newEntry(actionLogout, host, cat, at, 0, descr) diff --git a/internal/worktime/entries_test.go b/internal/worktime/entries_test.go index 1327a1a..f49dbd8 100644 --- a/internal/worktime/entries_test.go +++ b/internal/worktime/entries_test.go @@ -1,6 +1,7 @@ package worktime import ( + "errors" "testing" "time" ) @@ -19,6 +20,8 @@ func TestLoginLogoutValidation(t *testing.T) { if _, err := Login(dbDir, host, "work", time.Unix(110, 0), "start again"); err == nil { t.Fatal("Login() error = nil, want already logged in error") + } else if !errors.Is(err, ErrAlreadyLoggedIn) { + t.Fatalf("Login() error = %v, want ErrAlreadyLoggedIn", err) } logoutEntry, err := Logout(dbDir, host, "work", time.Unix(120, 0), "stop") @@ -31,6 +34,8 @@ func TestLoginLogoutValidation(t *testing.T) { if _, err := Logout(dbDir, host, "work", time.Unix(130, 0), "stop again"); err == nil { t.Fatal("Logout() error = nil, want not logged in error") + } else if !errors.Is(err, ErrNotLoggedIn) { + t.Fatalf("Logout() error = %v, want ErrNotLoggedIn", err) } } |
