156 lines
4.1 KiB
Go
156 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func newTestClient(t *testing.T, h http.HandlerFunc) (*Client, *httptest.Server) {
|
|
t.Helper()
|
|
srv := httptest.NewServer(h)
|
|
t.Cleanup(srv.Close)
|
|
c := &Client{
|
|
BaseURL: srv.URL,
|
|
Key: "test-key",
|
|
HTTP: srv.Client(),
|
|
}
|
|
return c, srv
|
|
}
|
|
|
|
func TestBasicAuthHeader(t *testing.T) {
|
|
want := "Basic " + base64.StdEncoding.EncodeToString([]byte("test-key:"))
|
|
var got string
|
|
c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
got = r.Header.Get("Authorization")
|
|
w.WriteHeader(200)
|
|
fmt.Fprint(w, `{}`)
|
|
})
|
|
if err := c.get(apiPrefix+"/accounts/1", nil, nil); err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if got != want {
|
|
t.Errorf("Authorization header:\n got=%q\nwant=%q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestGetErrorFormatting(t *testing.T) {
|
|
c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(401)
|
|
fmt.Fprint(w, `forbidden`)
|
|
})
|
|
err := c.get(apiPrefix+"/accounts/1", nil, nil)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
msg := err.Error()
|
|
for _, want := range []string{"GET ", "401", "forbidden", apiPrefix + "/accounts/1"} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("error %q missing %q", msg, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPathIDEncoding verifies IDs containing '/' are encoded per spec
|
|
// (e.g. john/doe -> john%2Fdoe) and the server sees the encoded form.
|
|
func TestPathIDEncoding(t *testing.T) {
|
|
var sawPath string
|
|
c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
sawPath = r.URL.EscapedPath()
|
|
w.WriteHeader(200)
|
|
fmt.Fprint(w, `{"id":"john/doe","name":"x"}`)
|
|
})
|
|
path := apiPrefix + "/accounts/" + pathID("john/doe")
|
|
if err := c.get(path, nil, nil); err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
want := apiPrefix + "/accounts/john%2Fdoe"
|
|
if sawPath != want {
|
|
t.Errorf("server saw path %q, want %q", sawPath, want)
|
|
}
|
|
}
|
|
|
|
// TestListAllPaginates walks 3 pages and concatenates rows.
|
|
func TestListAllPaginates(t *testing.T) {
|
|
c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
q, _ := url.ParseQuery(r.URL.RawQuery)
|
|
page, _ := strconv.Atoi(q.Get("page"))
|
|
if page == 0 {
|
|
page = 1
|
|
}
|
|
// 3 pages, 2 items each.
|
|
const pageCount = 3
|
|
if page > pageCount {
|
|
fmt.Fprintf(w, `{"data":[],"paginator":{"page":%d,"page_count":%d,"limit":2,"total_count":6}}`, page, pageCount)
|
|
return
|
|
}
|
|
a := (page-1)*2 + 1
|
|
b := a + 1
|
|
fmt.Fprintf(w, `{"data":[{"id":"%d","name":"n%d"},{"id":"%d","name":"n%d"}],"paginator":{"page":%d,"page_count":%d,"limit":2,"total_count":6}}`,
|
|
a, a, b, b, page, pageCount)
|
|
})
|
|
items, p, err := c.listAll("/accounts", 0, decodeListPage)
|
|
if err != nil {
|
|
t.Fatalf("listAll: %v", err)
|
|
}
|
|
if len(items) != 6 {
|
|
t.Errorf("got %d items, want 6", len(items))
|
|
}
|
|
// Verify last item is what we expect and ordering is preserved.
|
|
var last Account
|
|
if err := json.Unmarshal(items[5], &last); err != nil {
|
|
t.Fatalf("decode last: %v", err)
|
|
}
|
|
if last.ID != "6" || last.Name != "n6" {
|
|
t.Errorf("last item = %+v, want id=6 name=n6", last)
|
|
}
|
|
if p.TotalCount != 6 {
|
|
t.Errorf("synthetic paginator total_count = %d, want 6", p.TotalCount)
|
|
}
|
|
}
|
|
|
|
func TestListAllStopsOnEmptyPage(t *testing.T) {
|
|
calls := 0
|
|
c, _ := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
// page_count=0 means we should stop immediately on empty data.
|
|
fmt.Fprint(w, `{"data":[],"paginator":{"page":1,"page_count":0,"limit":0,"total_count":0}}`)
|
|
})
|
|
items, _, err := c.listAll("/accounts", 0, decodeListPage)
|
|
if err != nil {
|
|
t.Fatalf("listAll: %v", err)
|
|
}
|
|
if len(items) != 0 {
|
|
t.Errorf("got %d items, want 0", len(items))
|
|
}
|
|
if calls != 1 {
|
|
t.Errorf("hit server %d times, want 1", calls)
|
|
}
|
|
}
|
|
|
|
func TestValidateLimit(t *testing.T) {
|
|
cases := []struct {
|
|
limit int
|
|
wantErr bool
|
|
}{
|
|
{0, false},
|
|
{100, false},
|
|
{500, false},
|
|
{1000, false},
|
|
{99, true},
|
|
{1001, true},
|
|
{-1, true},
|
|
}
|
|
for _, tc := range cases {
|
|
err := validateLimit(tc.limit)
|
|
if (err != nil) != tc.wantErr {
|
|
t.Errorf("validateLimit(%d) err=%v, wantErr=%v", tc.limit, err, tc.wantErr)
|
|
}
|
|
}
|
|
}
|