a love letter to tangled (android, iOS, and a search API)
1package xrpc
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "net/http"
8 "net/http/httptest"
9 "net/url"
10 "testing"
11 "time"
12)
13
14func TestCall_Success(t *testing.T) {
15 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16 if r.URL.Path != "/xrpc/com.example.method" {
17 t.Errorf("unexpected path: %s", r.URL.Path)
18 }
19 if r.URL.Query().Get("key") != "val" {
20 t.Errorf("unexpected query: %s", r.URL.RawQuery)
21 }
22 w.WriteHeader(200)
23 w.Write([]byte(`{"ok":true}`))
24 }))
25 defer srv.Close()
26
27 c := NewClient()
28 body, err := c.Call(context.Background(), srv.URL, "com.example.method", url.Values{"key": {"val"}})
29 if err != nil {
30 t.Fatal(err)
31 }
32 defer body.Close()
33
34 var resp map[string]bool
35 json.NewDecoder(body).Decode(&resp)
36 if !resp["ok"] {
37 t.Error("expected ok=true")
38 }
39}
40
41func TestCall_404_ReturnsNotFoundError(t *testing.T) {
42 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
43 w.WriteHeader(404)
44 }))
45 defer srv.Close()
46
47 c := NewClient()
48 _, err := c.Call(context.Background(), srv.URL, "com.example.notfound", nil)
49 if err == nil {
50 t.Fatal("expected error")
51 }
52 var nfe *NotFoundError
53 if !errors.As(err, &nfe) {
54 t.Errorf("expected NotFoundError, got %T: %v", err, err)
55 }
56}
57
58func TestCall_429_ReturnsRateLimitError(t *testing.T) {
59 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60 w.Header().Set("Retry-After", "30")
61 w.WriteHeader(429)
62 }))
63 defer srv.Close()
64
65 c := NewClient()
66 _, err := c.Call(context.Background(), srv.URL, "com.example.ratelimit", nil)
67 if err == nil {
68 t.Fatal("expected error")
69 }
70 var rle *RateLimitError
71 if !errors.As(err, &rle) {
72 t.Fatalf("expected RateLimitError, got %T: %v", err, err)
73 }
74 if rle.RetryAfter != 30*time.Second {
75 t.Errorf("expected RetryAfter=30s, got %s", rle.RetryAfter)
76 }
77}
78
79func TestCall_500_ReturnsXRPCError(t *testing.T) {
80 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81 w.WriteHeader(500)
82 w.Write([]byte(`{"message":"internal error"}`))
83 }))
84 defer srv.Close()
85
86 c := NewClient()
87 _, err := c.Call(context.Background(), srv.URL, "com.example.error", nil)
88 if err == nil {
89 t.Fatal("expected error")
90 }
91 var xe *XRPCError
92 if !errors.As(err, &xe) {
93 t.Fatalf("expected XRPCError, got %T: %v", err, err)
94 }
95 if xe.StatusCode != 500 {
96 t.Errorf("expected status 500, got %d", xe.StatusCode)
97 }
98 if xe.Message != "internal error" {
99 t.Errorf("expected message 'internal error', got %q", xe.Message)
100 }
101}
102
103func TestParseRetryAfter(t *testing.T) {
104 if d := parseRetryAfter(""); d != 0 {
105 t.Errorf("empty: expected 0, got %s", d)
106 }
107 if d := parseRetryAfter("60"); d != 60*time.Second {
108 t.Errorf("numeric: expected 60s, got %s", d)
109 }
110}