1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
package api
import (
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
)
const widgetFixture = `{
"day": {"blockSize": 30, "timeFormat": "24h", "timezone": "America/Los_Angeles"},
"activityTypes": [
{"id": "t1", "name": "ARCHER", "color": "#c04000", "isArchived": false, "displayOrder": 0},
{"id": "t2", "name": "Foofaraw", "color": "#4000c0", "isArchived": false, "displayOrder": 1}
],
"blocks": {"28": "t1"},
"planBlocks": {"29": "t2"},
"events": [],
"extraEvents": {}
}`
func newTestServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server, *Client) {
t.Helper()
srv := httptest.NewServer(handler)
t.Cleanup(srv.Close)
return srv, New(srv.URL, "tok123")
}
func TestGetDay(t *testing.T) {
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/widget/data" {
t.Errorf("path %s", r.URL.Path)
}
if r.Header.Get("Authorization") != "Bearer tok123" {
t.Errorf("auth header %q", r.Header.Get("Authorization"))
}
if r.URL.Query().Get("date") != "2026-07-29" || r.URL.Query().Get("tz") == "" {
t.Errorf("query %s", r.URL.RawQuery)
}
w.Write([]byte(widgetFixture))
})
d, err := c.GetDay("2026-07-29", "America/Los_Angeles")
if err != nil {
t.Fatal(err)
}
if d.BlockSize != 30 || len(d.Types) != 2 || d.Blocks[28] != "t1" || d.PlanBlocks[29] != "t2" {
t.Fatalf("parsed %+v", d)
}
}
func TestGetDayUnauthorized(t *testing.T) {
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
})
_, err := c.GetDay("2026-07-29", "UTC")
if !errors.Is(err, ErrUnauthorized) {
t.Fatalf("want ErrUnauthorized, got %v", err)
}
}
func TestGetDayForbidden(t *testing.T) {
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
})
_, err := c.GetDay("2026-07-29", "UTC")
if !errors.Is(err, ErrForbidden) {
t.Fatalf("want ErrForbidden, got %v", err)
}
}
func TestWriteBlocks(t *testing.T) {
var got map[string]*string
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut || r.URL.Path != "/api/days/2026-07-29/blocks" {
t.Errorf("%s %s", r.Method, r.URL.Path)
}
json.NewDecoder(r.Body).Decode(&got)
w.Write([]byte(`{}`))
})
tid := "t2"
if err := c.WriteBlocks("2026-07-29", map[int]*string{29: &tid, 28: nil}); err != nil {
t.Fatal(err)
}
if *got["29"] != "t2" || got["28"] != nil {
t.Fatalf("body %v", got)
}
}
func TestAddNote(t *testing.T) {
var got map[string]any
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/api/days/2026-07-29/notes" {
t.Errorf("%s %s", r.Method, r.URL.Path)
}
json.NewDecoder(r.Body).Decode(&got)
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{}`))
})
if err := c.AddNote("2026-07-29", "hello"); err != nil {
t.Fatal(err)
}
if got["content"] != "hello" {
t.Fatalf("body %v", got)
}
}
func TestGetDayMissingBlockFields(t *testing.T) {
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
// Payload without blocks and planBlocks keys
w.Write([]byte(`{"day":{"blockSize":30,"timezone":"UTC"},"activityTypes":[]}`))
})
d, err := c.GetDay("2026-07-29", "UTC")
if err != nil {
t.Fatal(err)
}
if d.Blocks == nil {
t.Fatal("Blocks should be non-nil empty map")
}
if len(d.Blocks) != 0 {
t.Fatalf("Blocks should be empty, got %v", d.Blocks)
}
if d.PlanBlocks == nil {
t.Fatal("PlanBlocks should be non-nil empty map")
}
if len(d.PlanBlocks) != 0 {
t.Fatalf("PlanBlocks should be empty, got %v", d.PlanBlocks)
}
}
func TestGetDayUnsupportedBlockSize(t *testing.T) {
_, c := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"day":{"blockSize":0,"timezone":"UTC"},"activityTypes":[]}`))
})
_, err := c.GetDay("2026-07-29", "UTC")
if err == nil {
t.Fatal("want error for unsupported block size, got nil")
}
}
|