133 lines
5.1 KiB
Go
133 lines
5.1 KiB
Go
package v1
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func setupTwitterAuthorizationRouter() *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.RedirectTrailingSlash = false
|
|
handler := NewTwitterChannelHandler(nil, nil, nil, nil)
|
|
r.POST("/api/v1/accounts/:account_id/twitter/authorization", handler.ChatwootAuthorization)
|
|
return r
|
|
}
|
|
|
|
func TestTwitterAuthorization_BadAccountID(t *testing.T) {
|
|
r := setupTwitterAuthorizationRouter()
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/v1/accounts/abc/twitter/authorization", nil)
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
var resp map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
errBody := resp["error"].(map[string]interface{})
|
|
assert.Contains(t, errBody["message"], "invalid account_id")
|
|
}
|
|
|
|
func TestTwitterAuthorization_ReturnsChatwootPayload(t *testing.T) {
|
|
t.Setenv("TWITTER_CONSUMER_KEY", "twitter-key")
|
|
t.Setenv("TWITTER_CONSUMER_SECRET", "twitter-secret")
|
|
t.Setenv("TWITTER_OAUTH_REQUEST_TOKEN_URL", "https://oauth.example.test/request_token")
|
|
t.Setenv("TWITTER_API_BASE_URL", "https://api.twitter.test")
|
|
t.Setenv("FRONTEND_URL", "https://app.example.test/")
|
|
|
|
originalClient := twitterAuthorizationHTTPClient
|
|
twitterAuthorizationHTTPClient = &http.Client{Transport: twitterAuthorizationRoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
assert.Equal(t, http.MethodPost, req.Method)
|
|
assert.Equal(t, "https://oauth.example.test/request_token", req.URL.String())
|
|
authHeader := req.Header.Get("Authorization")
|
|
assert.Contains(t, authHeader, "OAuth ")
|
|
assert.Contains(t, authHeader, `oauth_consumer_key="twitter-key"`)
|
|
assert.Contains(t, authHeader, "oauth_callback=")
|
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("oauth_token=request-token&oauth_token_secret=request-secret")), Header: http.Header{}}, nil
|
|
})}
|
|
t.Cleanup(func() { twitterAuthorizationHTTPClient = originalClient })
|
|
|
|
r := setupTwitterAuthorizationRouter()
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/v1/accounts/42/twitter/authorization", nil)
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
assert.Equal(t, true, resp["success"])
|
|
assert.Equal(t, "https://api.twitter.test/oauth/authorize?oauth_token=request-token", resp["url"])
|
|
}
|
|
|
|
func TestTwitterAuthorization_SignedStateMatchesCallback(t *testing.T) {
|
|
t.Setenv("TWITTER_CONSUMER_KEY", "twitter-key")
|
|
t.Setenv("TWITTER_CONSUMER_SECRET", "twitter-secret")
|
|
t.Setenv("TWITTER_OAUTH_REQUEST_TOKEN_URL", "https://oauth.example.test/request_token")
|
|
t.Setenv("FRONTEND_URL", "https://app.example.test")
|
|
|
|
originalClient := twitterAuthorizationHTTPClient
|
|
var callbackURL string
|
|
twitterAuthorizationHTTPClient = &http.Client{Transport: twitterAuthorizationRoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
|
authHeader := req.Header.Get("Authorization")
|
|
for _, part := range strings.Split(strings.TrimPrefix(authHeader, "OAuth "), ",") {
|
|
part = strings.TrimSpace(part)
|
|
if strings.HasPrefix(part, "oauth_callback=") {
|
|
callbackURL = strings.Trim(strings.TrimPrefix(part, "oauth_callback="), `"`)
|
|
decoded, err := url.QueryUnescape(callbackURL)
|
|
require.NoError(t, err)
|
|
callbackURL = decoded
|
|
}
|
|
}
|
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("oauth_token=request-token")), Header: http.Header{}}, nil
|
|
})}
|
|
t.Cleanup(func() { twitterAuthorizationHTTPClient = originalClient })
|
|
|
|
r := setupTwitterAuthorizationRouter()
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/v1/accounts/42/twitter/authorization", nil)
|
|
r.ServeHTTP(w, req)
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
parsed, err := url.Parse(callbackURL)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "https://app.example.test/twitter/callback", parsed.Scheme+"://"+parsed.Host+parsed.Path)
|
|
claims := jwt.MapClaims{}
|
|
token, err := jwt.ParseWithClaims(parsed.Query().Get("state"), claims, func(token *jwt.Token) (any, error) {
|
|
return []byte("twitter-secret"), nil
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, token.Valid)
|
|
assert.Equal(t, float64(42), claims["sub"])
|
|
}
|
|
|
|
func TestTwitterAuthorization_NotConfigured(t *testing.T) {
|
|
t.Setenv("TWITTER_CONSUMER_KEY", "")
|
|
t.Setenv("TWITTER_CONSUMER_SECRET", "")
|
|
r := setupTwitterAuthorizationRouter()
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/v1/accounts/42/twitter/authorization", nil)
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnprocessableEntity, w.Code)
|
|
var resp map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
assert.Equal(t, false, resp["success"])
|
|
}
|
|
|
|
type twitterAuthorizationRoundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f twitterAuthorizationRoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
return f(req)
|
|
}
|