network/preseem/client_test.go
2026-07-19 14:42:02 -05:00

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