137 lines
4.2 KiB
Go
137 lines
4.2 KiB
Go
package discord
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"golang.org/x/time/rate"
|
||
|
|
)
|
||
|
|
|
||
|
|
func testClient(t *testing.T, handler http.Handler) *Client {
|
||
|
|
t.Helper()
|
||
|
|
server := httptest.NewServer(handler)
|
||
|
|
t.Cleanup(server.Close)
|
||
|
|
return &Client{
|
||
|
|
httpClient: server.Client(),
|
||
|
|
apiBase: server.URL,
|
||
|
|
limiter: rate.NewLimiter(rate.Inf, 1),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExchangeCodeAndGetUser(t *testing.T) {
|
||
|
|
client := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
switch r.URL.Path {
|
||
|
|
case "/oauth2/token":
|
||
|
|
user, pass, ok := r.BasicAuth()
|
||
|
|
if !ok || user != "client-id" || pass != "client-secret" {
|
||
|
|
t.Errorf("missing or wrong basic auth: %s/%s", user, pass)
|
||
|
|
}
|
||
|
|
if err := r.ParseForm(); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if r.PostForm.Get("grant_type") != "authorization_code" || r.PostForm.Get("code") != "the-code" {
|
||
|
|
t.Errorf("unexpected form: %v", r.PostForm)
|
||
|
|
}
|
||
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"access_token": "user-token"})
|
||
|
|
case "/users/@me":
|
||
|
|
if got := r.Header.Get("Authorization"); got != "Bearer user-token" {
|
||
|
|
t.Errorf("unexpected authorization %q", got)
|
||
|
|
}
|
||
|
|
_ = json.NewEncoder(w).Encode(User{ID: "42", Username: "quick"})
|
||
|
|
default:
|
||
|
|
t.Errorf("unexpected path %s", r.URL.Path)
|
||
|
|
w.WriteHeader(http.StatusNotFound)
|
||
|
|
}
|
||
|
|
}))
|
||
|
|
|
||
|
|
token, err := client.ExchangeCode(context.Background(), "client-id", "client-secret", "the-code", "https://silo.example/cb")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("exchange: %v", err)
|
||
|
|
}
|
||
|
|
user, err := client.GetUser(context.Background(), token)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("get user: %v", err)
|
||
|
|
}
|
||
|
|
if user.ID != "42" || user.Username != "quick" {
|
||
|
|
t.Fatalf("unexpected user %+v", user)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestOpenDMChannelAndSendDM(t *testing.T) {
|
||
|
|
client := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
switch r.URL.Path {
|
||
|
|
case "/users/@me/channels":
|
||
|
|
if got := r.Header.Get("Authorization"); got != "Bot bot-token" {
|
||
|
|
t.Errorf("unexpected authorization %q", got)
|
||
|
|
}
|
||
|
|
var body map[string]string
|
||
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
||
|
|
if body["recipient_id"] != "42" {
|
||
|
|
t.Errorf("unexpected recipient %q", body["recipient_id"])
|
||
|
|
}
|
||
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"id": "dm-123"})
|
||
|
|
case "/channels/dm-123/messages":
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
default:
|
||
|
|
t.Errorf("unexpected path %s", r.URL.Path)
|
||
|
|
w.WriteHeader(http.StatusNotFound)
|
||
|
|
}
|
||
|
|
}))
|
||
|
|
|
||
|
|
channelID, err := client.OpenDMChannel(context.Background(), "bot-token", "42")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("open dm: %v", err)
|
||
|
|
}
|
||
|
|
if channelID != "dm-123" {
|
||
|
|
t.Fatalf("unexpected channel id %q", channelID)
|
||
|
|
}
|
||
|
|
if err := client.SendDM(context.Background(), "bot-token", channelID, []byte(`{"content":"hi"}`)); err != nil {
|
||
|
|
t.Fatalf("send dm: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestErrorMapping(t *testing.T) {
|
||
|
|
cases := []struct {
|
||
|
|
name string
|
||
|
|
status int
|
||
|
|
body string
|
||
|
|
want error
|
||
|
|
}{
|
||
|
|
{"dm blocked", http.StatusForbidden, `{"code":50007,"message":"Cannot send messages to this user"}`, ErrDMBlocked},
|
||
|
|
{"bad token", http.StatusUnauthorized, `{"message":"401: Unauthorized"}`, ErrUnauthorized},
|
||
|
|
{"rate limited", http.StatusTooManyRequests, `{"message":"You are being rate limited."}`, ErrRateLimited},
|
||
|
|
}
|
||
|
|
for _, tc := range cases {
|
||
|
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
|
client := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
|
w.WriteHeader(tc.status)
|
||
|
|
_, _ = w.Write([]byte(tc.body))
|
||
|
|
}))
|
||
|
|
err := client.SendDM(context.Background(), "bot-token", "dm-123", []byte(`{}`))
|
||
|
|
if !errors.Is(err, tc.want) {
|
||
|
|
t.Fatalf("got %v, want %v", err, tc.want)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLimiterHonorsContextCancel(t *testing.T) {
|
||
|
|
client := testClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
}))
|
||
|
|
// Exhausted limiter with a long refill forces Wait to block on the context.
|
||
|
|
client.limiter = rate.NewLimiter(rate.Every(time.Hour), 1)
|
||
|
|
client.limiter.Allow()
|
||
|
|
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||
|
|
defer cancel()
|
||
|
|
if err := client.SendDM(ctx, "bot-token", "dm-123", []byte(`{}`)); err == nil {
|
||
|
|
t.Fatal("expected context error from limiter wait")
|
||
|
|
}
|
||
|
|
}
|