diff --git a/go.mod b/go.mod index e4cc239..4f79872 100644 --- a/go.mod +++ b/go.mod @@ -16,5 +16,5 @@ require ( github.com/tevino/abool v1.2.0 // indirect golang.org/x/image v0.10.0 // indirect golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 0adf26d..ce23286 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,19 @@ fyne.io/systray v1.10.0 h1:Yr1D9Lxeiw3+vSuZWPlaHC8BMjIHZXJKkek706AfYQk= fyne.io/systray v1.10.0/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= +fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= +fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 h1:1ltqoej5GtaWF8jaiA49HwsZD459jqm9YFz9ZtMFpQA= github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= @@ -17,6 +23,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M= golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= +golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= +golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -26,6 +34,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -38,6 +48,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/main.go b/main.go index e423778..2f5da9f 100644 --- a/main.go +++ b/main.go @@ -142,26 +142,21 @@ func onExit() { func (c *Settings) getUserDetail() error { type UserResponse struct { - Data struct { - ID int32 `json:"id"` - Workspaces []Workspaces `json:"workspaces"` - } `json:"data"` + ID int32 `json:"id"` + Workspaces []Workspaces `json:"workspaces"` } var ur UserResponse - toggl := resty.New().SetHostURL("https://api.track.toggl.com/api/v8").SetBasicAuth(c.Token, "api_token") + toggl := resty.New().SetHostURL("https://api.track.toggl.com/api/v9").SetBasicAuth(c.Token, "api_token") _, err := toggl.R(). - SetQueryParams(map[string]string{ - "user_agent": c.Email, - }). SetResult(&ur). Get("/me?with_related_data=true") if err != nil { return fmt.Errorf("unable to get user details from the Toggl API: %v", err) } - c.UserID = fmt.Sprint(ur.Data.ID) - c.Workspaces = ur.Data.Workspaces + c.UserID = fmt.Sprint(ur.ID) + c.Workspaces = ur.Workspaces return nil } @@ -194,7 +189,7 @@ func getClosedTimeEntries(c *Settings, w string) (time.Duration, error) { "user_agent": c.Email, "workspace_id": w, "user_ids": c.UserID, - "since": getLastMonday(), + "since": getLastMonday(time.Now()), }). SetResult(&ct). Get("/weekly") @@ -207,43 +202,36 @@ func getClosedTimeEntries(c *Settings, w string) (time.Duration, error) { func getOpenTimeEntry(c *Settings, w string) (time.Duration, error) { type TimeEntriesResponse struct { - Data struct { - WID int32 `json:"wid"` - Duration int32 `json:"duration"` - } `json:"data"` + WID int32 `json:"wid"` + Duration int32 `json:"duration"` } var ot TimeEntriesResponse - toggl := resty.New().SetHostURL("https://api.track.toggl.com/api/v8").SetBasicAuth(c.Token, "api_token") + toggl := resty.New().SetHostURL("https://api.track.toggl.com/api/v9").SetBasicAuth(c.Token, "api_token") _, err := toggl.R(). - SetQueryParams(map[string]string{ - "user_agent": c.Email, - "wid": w, - }). SetResult(&ot). - Get("/time_entries/current") + Get("/me/time_entries/current") if err != nil { return time.Duration(0), fmt.Errorf("unable to get current time entry from the Toggl API: %v", err) } // if the returned duration is not negative then there is no open entry. // we also filter entries that do not match the workspace here. - if ot.Data.Duration >= 0 || fmt.Sprint(ot.Data.WID) != w { + if ot.Duration >= 0 || fmt.Sprint(ot.WID) != w { return 0, nil } // Calculate the number of seconds based on the input data. // Unix epoch plus returned value of duration = seconds the current entry has been running for. - od := int32(time.Now().Unix()) + ot.Data.Duration + od := int32(time.Now().Unix()) + ot.Duration return time.Duration(od) * time.Second, nil } -func getLastMonday() string { - t := time.Now() - delta := (int(t.Weekday()) - 1) * -1 - t = t.AddDate(0, 0, delta) +func getLastMonday(t time.Time) string { + delta := (int(t.Weekday()) + 6) % 7 + t = t.AddDate(0, 0, -delta) return t.Format("2006-01-02") } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..cec0d3f --- /dev/null +++ b/main_test.go @@ -0,0 +1,181 @@ +package main + +import ( + "testing" + "time" +) + +func TestGetLastMonday(t *testing.T) { + tests := []struct { + name string + date time.Time + expected string + }{ + { + name: "Monday", + date: time.Date(2022, time.January, 3, 0, 0, 0, 0, time.UTC), + expected: "2022-01-03", + }, + { + name: "Tuesday", + date: time.Date(2022, time.January, 4, 0, 0, 0, 0, time.UTC), + expected: "2022-01-03", + }, + { + name: "Sunday", + date: time.Date(2022, time.January, 9, 0, 0, 0, 0, time.UTC), + expected: "2022-01-03", + }, + { + name: "Leap year February 29", + date: time.Date(2024, time.February, 29, 0, 0, 0, 0, time.UTC), + expected: "2024-02-26", + }, + { + name: "Week spanning two months", + date: time.Date(2022, time.January, 31, 0, 0, 0, 0, time.UTC), + expected: "2022-01-31", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getLastMonday(tt.date) + if result != tt.expected { + t.Errorf("Expected %s, but got %s", tt.expected, result) + } + }) + } +} +func TestGetHours(t *testing.T) { + tests := []struct { + name string + duration time.Duration + expected int + }{ + { + name: "Zero duration", + duration: 0, + expected: 0, + }, + { + name: "Less than an hour", + duration: time.Minute * 30, + expected: 0, + }, + { + name: "Exactly one hour", + duration: time.Hour, + expected: 1, + }, + { + name: "More than one hour", + duration: time.Hour + time.Minute*30, + expected: 1, + }, + { + name: "Multiple hours", + duration: time.Hour*3 + time.Minute*45, + expected: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getHours(tt.duration) + if result != tt.expected { + t.Errorf("Expected %d, but got %d", tt.expected, result) + } + }) + } +} +func TestGetMinutes(t *testing.T) { + tests := []struct { + name string + duration time.Duration + expected int + }{ + { + name: "Zero duration", + duration: 0, + expected: 0, + }, + { + name: "Less than a minute rounds up", + duration: time.Second * 30, + expected: 1, + }, + { + name: "Less than a minute rounds down", + duration: time.Second * 20, + expected: 0, + }, + { + name: "Exactly one minute", + duration: time.Minute, + expected: 1, + }, + { + name: "More than one minute", + duration: time.Minute + time.Second*30, + expected: 2, + }, + { + name: "Multiple minutes", + duration: time.Minute*3 + time.Second*45, + expected: 4, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getMinutes(tt.duration) + if result != tt.expected { + t.Errorf("Expected %d, but got %d", tt.expected, result) + } + }) + } +} + +func TestAdd(t *testing.T) { + tests := []struct { + name string + t1 togglTime + t2 togglTime + expected togglTime + }{ + { + name: "Adding zero time", + t1: togglTime{hours: 1, minutes: 30}, + t2: togglTime{hours: 0, minutes: 0}, + expected: togglTime{hours: 1, minutes: 30}, + }, + { + name: "Adding minutes", + t1: togglTime{hours: 1, minutes: 30}, + t2: togglTime{hours: 0, minutes: 45}, + expected: togglTime{hours: 2, minutes: 15}, + }, + { + name: "Adding hours", + t1: togglTime{hours: 1, minutes: 30}, + t2: togglTime{hours: 2, minutes: 0}, + expected: togglTime{hours: 3, minutes: 30}, + }, + { + name: "Adding hours and minutes", + t1: togglTime{hours: 1, minutes: 30}, + t2: togglTime{hours: 2, minutes: 45}, + expected: togglTime{hours: 4, minutes: 15}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := togglTime{} + result.add(tt.t1) + result.add(tt.t2) + if result != tt.expected { + t.Errorf("Expected %v, but got %v", tt.expected, result) + } + }) + } +}