feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
This commit is contained in:
@@ -0,0 +1,649 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func parseRequestForm(t *testing.T, req *http.Request) url.Values {
|
||||
t.Helper()
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read request body failed: %v", err)
|
||||
}
|
||||
values, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
t.Fatalf("parse request body failed: %v", err)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func TestPostFormWithContextSendsEncodedBody(t *testing.T) {
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method != http.MethodPost {
|
||||
t.Fatalf("expected POST request, got %s", req.Method)
|
||||
}
|
||||
if req.URL.String() != "https://oauth.example.com/token" {
|
||||
t.Fatalf("unexpected endpoint: %s", req.URL.String())
|
||||
}
|
||||
if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
|
||||
t.Fatalf("unexpected content type: %s", req.Header.Get("Content-Type"))
|
||||
}
|
||||
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("code") != "auth-code" || form.Get("grant_type") != "authorization_code" {
|
||||
t.Fatalf("unexpected form payload: %#v", form)
|
||||
}
|
||||
|
||||
return oauthResponse(`{"ok":true}`), nil
|
||||
}),
|
||||
}
|
||||
|
||||
resp, err := postFormWithContext(context.Background(), client, "https://oauth.example.com/token", url.Values{
|
||||
"code": {"auth-code"},
|
||||
"grant_type": {"authorization_code"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("postFormWithContext failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
func TestAlipayProviderExchangeCodeAndGetUserInfo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewAlipayProvider("alipay-app", "", "https://example.com/callback", false)
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "openapi.alipay.com" || req.URL.Path != "/gateway.do" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("method") != "alipay.system.oauth.token" || form.Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected exchange payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"alipay_system_oauth_token_response":{"user_id":"2088","access_token":"ali-token","expires_in":3600}}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "ali-token" || tokenResp.UserID != "2088" {
|
||||
t.Fatalf("unexpected alipay token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exchange code rejects invalid structure", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"unexpected":{}}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid alipay response structure") {
|
||||
t.Fatalf("expected invalid structure error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "openapi.alipay.com" || req.URL.Path != "/gateway.do" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("method") != "alipay.user.info.share" || form.Get("auth_token") != "ali-token" {
|
||||
t.Fatalf("unexpected user-info payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"alipay_user_info_share_response":{"user_id":"2088","nick_name":"Ali User","avatar":"https://cdn.example.com/avatar.png"}}`), nil
|
||||
}))
|
||||
|
||||
userInfo, err := provider.GetUserInfo(ctx, "ali-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected user info success, got error %v", err)
|
||||
}
|
||||
if userInfo.UserID != "2088" || userInfo.Nickname != "Ali User" {
|
||||
t.Fatalf("unexpected alipay user info: %#v", userInfo)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info rejects invalid structure", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"unexpected":{}}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.GetUserInfo(ctx, "ali-token")
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid alipay user info response") {
|
||||
t.Fatalf("expected invalid user info response error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDouyinProviderExchangeCodeAndGetUserInfo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewDouyinProvider("douyin-key", "douyin-secret", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "open.douyin.com" || req.URL.Path != "/oauth/access_token/" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("client_key") != "douyin-key" || form.Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected exchange payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"data":{"access_token":"douyin-token","open_id":"open-1"},"message":"success"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.Data.AccessToken != "douyin-token" || tokenResp.Data.OpenID != "open-1" {
|
||||
t.Fatalf("unexpected douyin token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exchange code rejects empty access token", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"data":{},"message":"invalid code"}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid code") {
|
||||
t.Fatalf("expected douyin api error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "open.douyin.com" || req.URL.Path != "/oauth/userinfo/" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
if req.URL.Query().Get("open_id") != "open-1" {
|
||||
t.Fatalf("unexpected open_id: %s", req.URL.Query().Get("open_id"))
|
||||
}
|
||||
return oauthResponse(`{"data":{"open_id":"open-1","union_id":"union-1","nickname":"Douyin User"}}`), nil
|
||||
}))
|
||||
|
||||
userInfo, err := provider.GetUserInfo(ctx, "douyin-token", "open-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected user info success, got error %v", err)
|
||||
}
|
||||
if userInfo.Data.OpenID != "open-1" || userInfo.Data.Nickname != "Douyin User" {
|
||||
t.Fatalf("unexpected douyin user info: %#v", userInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGitHubProviderExchangeCodeAndGetUserInfo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewGitHubProvider("github-client", "github-secret", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "github.com" || req.URL.Path != "/login/oauth/access_token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("client_id") != "github-client" || form.Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected exchange payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"access_token":"gh-token","token_type":"bearer","scope":"read:user"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "gh-token" {
|
||||
t.Fatalf("unexpected github token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exchange code rejects empty token", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"token_type":"bearer"}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err == nil || !strings.Contains(err.Error(), "empty access token") {
|
||||
t.Fatalf("expected empty access token error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info falls back to primary email", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Host + req.URL.Path {
|
||||
case "api.github.com/user":
|
||||
if req.Header.Get("Authorization") != "Bearer gh-token" {
|
||||
t.Fatalf("unexpected auth header: %s", req.Header.Get("Authorization"))
|
||||
}
|
||||
return oauthResponse(`{"id":101,"login":"octocat","name":"The Octocat","email":"","avatar_url":"https://cdn.example.com/octocat.png"}`), nil
|
||||
case "api.github.com/user/emails":
|
||||
return oauthResponse(`[{"email":"secondary@example.com","primary":false,"verified":true},{"email":"primary@example.com","primary":true,"verified":true}]`), nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
return nil, nil
|
||||
}
|
||||
}))
|
||||
|
||||
userInfo, err := provider.GetUserInfo(ctx, "gh-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected user info success, got error %v", err)
|
||||
}
|
||||
if userInfo.Login != "octocat" || userInfo.Email != "primary@example.com" {
|
||||
t.Fatalf("unexpected github user info: %#v", userInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleProviderExchangeCodeAndRefreshToken(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewGoogleProvider("google-client", "google-secret", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "oauth2.googleapis.com" || req.URL.Path != "/token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("grant_type") != "authorization_code" || form.Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected exchange payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"access_token":"google-token","expires_in":3600,"refresh_token":"refresh-1","token_type":"Bearer"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "google-token" || tokenResp.RefreshToken != "refresh-1" {
|
||||
t.Fatalf("unexpected google token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("refresh token success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "oauth2.googleapis.com" || req.URL.Path != "/token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("grant_type") != "refresh_token" || form.Get("refresh_token") != "refresh-1" {
|
||||
t.Fatalf("unexpected refresh payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"access_token":"google-token-2","expires_in":3600,"token_type":"Bearer"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.RefreshToken(ctx, "refresh-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected refresh success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "google-token-2" {
|
||||
t.Fatalf("unexpected google refresh response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestQQProviderExchangeCodeAndValidateToken(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewQQProvider("qq-app", "qq-secret", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "graph.qq.com" || req.URL.Path != "/oauth2.0/token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
if req.URL.Query().Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected code: %s", req.URL.Query().Get("code"))
|
||||
}
|
||||
return oauthResponse(`{"access_token":"qq-token","expires_in":3600,"refresh_token":"qq-refresh"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "qq-token" || tokenResp.RefreshToken != "qq-refresh" {
|
||||
t.Fatalf("unexpected qq token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("validate token success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "graph.qq.com" || req.URL.Path != "/oauth2.0/me" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"client_id":"qq-app","openid":"openid-1"}`), nil
|
||||
}))
|
||||
|
||||
valid, err := provider.ValidateToken(ctx, "qq-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected validate success, got error %v", err)
|
||||
}
|
||||
if !valid {
|
||||
t.Fatal("expected qq token to be valid")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTwitterProviderNetworkMethods(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewTwitterProvider("twitter-client", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code rejects twitter error response", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.twitter.com" || req.URL.Path != "/2/oauth2/token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("grant_type") != "authorization_code" || form.Get("code_verifier") != "verifier-1" {
|
||||
t.Fatalf("unexpected exchange payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"title":"Unauthorized","detail":"invalid verifier","status":401}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.ExchangeCode(ctx, "auth-code", "verifier-1")
|
||||
if err == nil || !strings.Contains(err.Error(), "invalid verifier") {
|
||||
t.Fatalf("expected twitter api error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"access_token":"twitter-token","refresh_token":"twitter-refresh","token_type":"bearer"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code", "verifier-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "twitter-token" {
|
||||
t.Fatalf("unexpected twitter token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info rejects twitter error response", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.twitter.com" || req.URL.Path != "/2/users/me" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"title":"Unauthorized","detail":"token expired","status":401}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.GetUserInfo(ctx, "twitter-token")
|
||||
if err == nil || !strings.Contains(err.Error(), "token expired") {
|
||||
t.Fatalf("expected twitter user info error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"data":{"id":"user-1","name":"Twitter User","username":"tw-user"}}`), nil
|
||||
}))
|
||||
|
||||
userInfo, err := provider.GetUserInfo(ctx, "twitter-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected user info success, got error %v", err)
|
||||
}
|
||||
if userInfo.Data.ID != "user-1" || userInfo.Data.Username != "tw-user" {
|
||||
t.Fatalf("unexpected twitter user info: %#v", userInfo)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("refresh token success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("grant_type") != "refresh_token" || form.Get("refresh_token") != "twitter-refresh" {
|
||||
t.Fatalf("unexpected refresh payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"access_token":"twitter-token-2","refresh_token":"twitter-refresh-2","token_type":"bearer"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.RefreshToken(ctx, "twitter-refresh")
|
||||
if err != nil {
|
||||
t.Fatalf("expected refresh success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "twitter-token-2" {
|
||||
t.Fatalf("unexpected twitter refresh response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("validate token returns false when user id is empty", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"data":{"id":"","username":"anonymous"}}`), nil
|
||||
}))
|
||||
|
||||
valid, err := provider.ValidateToken(ctx, "twitter-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if valid {
|
||||
t.Fatal("expected twitter token to be reported invalid")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("revoke token success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.twitter.com" || req.URL.Path != "/2/oauth2/revoke" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("token") != "twitter-token" || form.Get("token_type_hint") != "access_token" {
|
||||
t.Fatalf("unexpected revoke payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{}`), nil
|
||||
}))
|
||||
|
||||
if err := provider.RevokeToken(ctx, "twitter-token"); err != nil {
|
||||
t.Fatalf("expected revoke success, got error %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWeChatProviderExchangeUserInfoAndRefreshToken(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewWeChatProvider("wx-app", "wx-secret", "web")
|
||||
|
||||
t.Run("exchange code rejects api error", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.weixin.qq.com" || req.URL.Path != "/sns/oauth2/access_token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"errcode":40029,"errmsg":"invalid code"}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err == nil || !strings.Contains(err.Error(), "wechat api error: 40029 - invalid code") {
|
||||
t.Fatalf("expected wechat api error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"access_token":"wx-token","refresh_token":"wx-refresh","openid":"openid-1","scope":"snsapi_login"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "wx-token" || tokenResp.OpenID != "openid-1" {
|
||||
t.Fatalf("unexpected wechat token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info rejects api error", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.weixin.qq.com" || req.URL.Path != "/sns/userinfo" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"errcode":40003,"errmsg":"invalid openid"}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.GetUserInfo(ctx, "wx-token", "openid-1")
|
||||
if err == nil || !strings.Contains(err.Error(), "wechat api error: 40003 - invalid openid") {
|
||||
t.Fatalf("expected wechat user info error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"openid":"openid-1","nickname":"WeChat User","province":"Shanghai"}`), nil
|
||||
}))
|
||||
|
||||
userInfo, err := provider.GetUserInfo(ctx, "wx-token", "openid-1")
|
||||
if err != nil {
|
||||
t.Fatalf("expected user info success, got error %v", err)
|
||||
}
|
||||
if userInfo.OpenID != "openid-1" || userInfo.Nickname != "WeChat User" {
|
||||
t.Fatalf("unexpected wechat user info: %#v", userInfo)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("refresh token rejects api error", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.weixin.qq.com" || req.URL.Path != "/sns/oauth2/refresh_token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"errcode":40030,"errmsg":"invalid refresh token"}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.RefreshToken(ctx, "wx-refresh")
|
||||
if err == nil || !strings.Contains(err.Error(), "wechat api error: 40030 - invalid refresh token") {
|
||||
t.Fatalf("expected wechat refresh error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("refresh token success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"access_token":"wx-token-2","refresh_token":"wx-refresh-2","openid":"openid-1"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.RefreshToken(ctx, "wx-refresh")
|
||||
if err != nil {
|
||||
t.Fatalf("expected refresh success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "wx-token-2" {
|
||||
t.Fatalf("unexpected wechat refresh response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWeiboProviderExchangeCodeAndGetUserInfo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewWeiboProvider("weibo-app", "weibo-secret", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.weibo.com" || req.URL.Path != "/oauth2/access_token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
form := parseRequestForm(t, req)
|
||||
if form.Get("client_id") != "weibo-app" || form.Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected exchange payload: %#v", form)
|
||||
}
|
||||
return oauthResponse(`{"access_token":"weibo-token","expires_in":3600,"uid":"1001"}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "weibo-token" || tokenResp.UID != "1001" {
|
||||
t.Fatalf("unexpected weibo token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info rejects api error", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "api.weibo.com" || req.URL.Path != "/2/users/show.json" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"error":1,"error_code":21315,"request":"/2/users/show.json"}`), nil
|
||||
}))
|
||||
|
||||
_, err := provider.GetUserInfo(ctx, "weibo-token", "1001")
|
||||
if err == nil || !strings.Contains(err.Error(), "weibo api error: code=21315") {
|
||||
t.Fatalf("expected weibo api error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return oauthResponse(`{"id":1001,"idstr":"1001","screen_name":"weibo-user","name":"Weibo User"}`), nil
|
||||
}))
|
||||
|
||||
userInfo, err := provider.GetUserInfo(ctx, "weibo-token", "1001")
|
||||
if err != nil {
|
||||
t.Fatalf("expected user info success, got error %v", err)
|
||||
}
|
||||
if userInfo.ID != 1001 || userInfo.ScreenName != "weibo-user" {
|
||||
t.Fatalf("unexpected weibo user info: %#v", userInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFacebookProviderExchangeValidateAndLongLivedToken(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
provider := NewFacebookProvider("facebook-app", "facebook-secret", "https://example.com/callback")
|
||||
|
||||
t.Run("exchange code success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Host != "graph.facebook.com" || req.URL.Path != "/v18.0/oauth/access_token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
if req.URL.Query().Get("code") != "auth-code" {
|
||||
t.Fatalf("unexpected code: %s", req.URL.Query().Get("code"))
|
||||
}
|
||||
return oauthResponse(`{"access_token":"fb-token","token_type":"bearer","expires_in":3600}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
|
||||
if err != nil {
|
||||
t.Fatalf("expected exchange success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "fb-token" {
|
||||
t.Fatalf("unexpected facebook token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("validate token returns false for empty id", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Path != "/v18.0/me" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"id":"","name":"No ID User"}`), nil
|
||||
}))
|
||||
|
||||
valid, err := provider.ValidateToken(ctx, "fb-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected validate success, got error %v", err)
|
||||
}
|
||||
if valid {
|
||||
t.Fatal("expected facebook token to be reported invalid")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get long lived token success", func(t *testing.T) {
|
||||
useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Path != "/v18.0/oauth/access_token" || req.URL.Query().Get("grant_type") != "fb_exchange_token" {
|
||||
t.Fatalf("unexpected request: %s", req.URL.String())
|
||||
}
|
||||
return oauthResponse(`{"access_token":"fb-long-lived","token_type":"bearer","expires_in":5184000}`), nil
|
||||
}))
|
||||
|
||||
tokenResp, err := provider.GetLongLivedToken(ctx, "fb-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected long-lived token success, got error %v", err)
|
||||
}
|
||||
if tokenResp.AccessToken != "fb-long-lived" {
|
||||
t.Fatalf("unexpected facebook long-lived token response: %#v", tokenResp)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user