summaryrefslogtreecommitdiff
path: root/internal/commands/out_test.go
diff options
context:
space:
mode:
authorT <t@tjp.lol>2025-08-22 15:52:06 -0600
committerT <t@tjp.lol>2025-08-28 22:02:13 -0600
commitadd7c1a8126733dd86282f443dc53127888c06af (patch)
tree7141166363b333a4c65e64785fdf4f5a08350a8d /internal/commands/out_test.go
parent275cbc0b30121d3273f7fd428583e8c48ce7d017 (diff)
loads of testing
Diffstat (limited to 'internal/commands/out_test.go')
-rw-r--r--internal/commands/out_test.go170
1 files changed, 170 insertions, 0 deletions
diff --git a/internal/commands/out_test.go b/internal/commands/out_test.go
index 03a9b73..2ea0d12 100644
--- a/internal/commands/out_test.go
+++ b/internal/commands/out_test.go
@@ -4,7 +4,9 @@ import (
"context"
"database/sql"
"errors"
+ "strings"
"testing"
+ "time"
"git.tjp.lol/punchcard/internal/queries"
)
@@ -122,3 +124,171 @@ func TestOutCommandErrorType(t *testing.T) {
}
}
+func TestOutCommandTimezoneHandling(t *testing.T) {
+ // Test that punch out stores UTC timestamps and calculates durations correctly
+ tests := []struct {
+ name string
+ setupData func(*queries.Queries) error
+ }{
+ {
+ name: "punch out stores UTC end timestamp",
+ setupData: func(q *queries.Queries) error {
+ client, err := q.CreateClient(context.Background(), queries.CreateClientParams{
+ Name: "TimezoneOutClient",
+ })
+ if err != nil {
+ return err
+ }
+
+ _, err = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{
+ Description: sql.NullString{String: "Timezone test work", Valid: true},
+ ClientID: client.ID,
+ })
+ return err
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ q, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ if err := tt.setupData(q); err != nil {
+ t.Fatalf("Failed to setup test data: %v", err)
+ }
+
+ // Get the active entry before stopping to compare
+ beforeEntry, err := q.GetActiveTimeEntry(context.Background())
+ if err != nil {
+ t.Fatalf("Failed to get active entry before stopping: %v", err)
+ }
+
+ // Execute out command
+ output, err := executeCommandWithDB(t, q, "out")
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // Verify output contains expected elements
+ if !strings.Contains(output, "Timer stopped") {
+ t.Errorf("Expected 'Timer stopped' in output, got: %s", output)
+ }
+
+ // Verify no active timer exists now
+ _, err = q.GetActiveTimeEntry(context.Background())
+ if !errors.Is(err, sql.ErrNoRows) {
+ t.Errorf("Expected no active timer after stopping, but got: %v", err)
+ }
+
+ // Get the stopped entry to verify timestamps
+ stoppedEntry, err := q.GetTimeEntryById(context.Background(), beforeEntry.ID)
+ if err != nil {
+ t.Fatalf("Failed to get stopped entry: %v", err)
+ }
+
+ // Verify start time is still in UTC
+ if stoppedEntry.StartTime.Location() != time.UTC {
+ t.Errorf("Expected start time to remain in UTC, got: %v", stoppedEntry.StartTime.Location())
+ }
+
+ // Verify end time is stored in UTC
+ if !stoppedEntry.EndTime.Valid {
+ t.Errorf("Expected end time to be set")
+ } else if stoppedEntry.EndTime.Time.Location() != time.UTC {
+ t.Errorf("Expected end time to be in UTC, got: %v", stoppedEntry.EndTime.Time.Location())
+ }
+
+ // Verify end time is after or equal to start time (can be equal due to millisecond precision)
+ if stoppedEntry.EndTime.Valid && stoppedEntry.EndTime.Time.Before(stoppedEntry.StartTime) {
+ t.Errorf("Expected end time %v to be >= start time %v", stoppedEntry.EndTime.Time, stoppedEntry.StartTime)
+ }
+ })
+ }
+}
+
+func TestOutCommandDurationCalculation(t *testing.T) {
+ // Test that duration calculations are timezone-independent
+ tests := []struct {
+ name string
+ setupData func(*queries.Queries) error
+ }{
+ {
+ name: "duration calculation works across timezone boundaries",
+ setupData: func(q *queries.Queries) error {
+ client, err := q.CreateClient(context.Background(), queries.CreateClientParams{
+ Name: "DurationTestClient",
+ })
+ if err != nil {
+ return err
+ }
+
+ _, err = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{
+ Description: sql.NullString{String: "Duration test work", Valid: true},
+ ClientID: client.ID,
+ })
+ return err
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ q, cleanup := setupTestDB(t)
+ defer cleanup()
+
+ if err := tt.setupData(q); err != nil {
+ t.Fatalf("Failed to setup test data: %v", err)
+ }
+
+ // Get start time
+ activeEntry, err := q.GetActiveTimeEntry(context.Background())
+ if err != nil {
+ t.Fatalf("Failed to get active entry: %v", err)
+ }
+ startTime := activeEntry.StartTime
+
+ // Wait a tiny bit to ensure non-zero duration (this is a simple test)
+ time.Sleep(time.Millisecond * 10)
+
+ // Execute out command
+ output, err := executeCommandWithDB(t, q, "out")
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // Parse the duration from output (it should contain something like "Session duration: 10ms")
+ if !strings.Contains(output, "Session duration:") {
+ t.Errorf("Expected duration in output, got: %s", output)
+ }
+
+ // Get the stopped entry
+ stoppedEntry, err := q.GetTimeEntryById(context.Background(), activeEntry.ID)
+ if err != nil {
+ t.Fatalf("Failed to get stopped entry: %v", err)
+ }
+
+ // Verify that duration can be calculated correctly from UTC times
+ if !stoppedEntry.EndTime.Valid {
+ t.Fatalf("Expected end time to be valid")
+ }
+
+ duration := stoppedEntry.EndTime.Time.Sub(stoppedEntry.StartTime)
+ if duration < 0 {
+ t.Errorf("Expected non-negative duration, got: %v", duration)
+ }
+
+ // Duration should be at least the sleep time we waited (with some tolerance)
+ // Note: In fast tests, the duration might be very small or even 0
+ if duration < 0 {
+ t.Errorf("Duration should not be negative, got: %v", duration)
+ }
+
+ // Verify times are in UTC for consistency
+ if startTime.Location() != time.UTC || stoppedEntry.EndTime.Time.Location() != time.UTC {
+ t.Errorf("Expected both times in UTC for consistent duration calculation")
+ }
+ })
+ }
+}
+