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") } }