a love letter to tangled (android, iOS, and a search API)
at main 110 lines 2.8 kB view raw
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}