package ibd
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/ansg191/ibd-trader/backend/internal/ibd/transport"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/html"
)
const extractAuthHtml = `
Log in ยท Dow Jones
`
func Test_extractAuthConfig(t *testing.T) {
t.Parallel()
expectedJSON := `
{
"auth0Domain": "sso.accounts.dowjones.com",
"callbackURL": "https://myibd.investors.com/oidc/callback",
"clientID": "GSU1pG2Brgd3Pv2KBnAZ24zvy5uWSCQn",
"extraParams": {
"protocol": "oauth2",
"scope": "openid idp_id roles email given_name family_name uuid djUsername djStatus trackid tags prts updated_at created_at offline_access djid",
"response_type": "code",
"nonce": "6402fabb-1b75-4a2c-a84f-11ad61aadb6b",
"ui_locales": "en-us-x-ibd-23-7",
"_csrf": "NdURgwOCuXENTEqCp8MWnmpkqwyokbcSa6U__-5boyVsSsASVNHKSA",
"_intstate": "deprecated",
"state": "earc7q6Rq6kyGKxy.Ymliq9N1EvoSUtz8CV8n0VAc6VsUxDIRM4SrlmIbW2k"
},
"internalOptions": {
"response_type": "code",
"client_id": "GSU1pG2Brgd3Pv2KBnAZ24zvy5uWSCQn",
"scope": "openid idp_id roles email given_name family_name uuid djUsername djStatus trackid tags prts updated_at created_at offline_access djid",
"redirect_uri": "https://myibd.investors.com/oidc/callback",
"ui_locales": "en-us-x-ibd-23-7",
"eurl": "https://www.investors.com",
"nonce": "6402fabb-1b75-4a2c-a84f-11ad61aadb6b",
"state": "earc7q6Rq6kyGKxy.Ymliq9N1EvoSUtz8CV8n0VAc6VsUxDIRM4SrlmIbW2k",
"resource": "https%3A%2F%2Fwww.investors.com",
"protocol": "oauth2",
"client": "GSU1pG2Brgd3Pv2KBnAZ24zvy5uWSCQn"
},
"isThirdPartyClient": false,
"authorizationServer": {
"url": "https://sso.accounts.dowjones.com",
"issuer": "https://sso.accounts.dowjones.com/"
}
}`
var expectedCfg authConfig
err := json.Unmarshal([]byte(expectedJSON), &expectedCfg)
require.NoError(t, err)
node, err := html.Parse(strings.NewReader(extractAuthHtml))
require.NoError(t, err)
cfg, err := extractAuthConfig(node)
require.NoError(t, err)
require.NotNil(t, cfg)
assert.Equal(t, expectedCfg, *cfg)
}
const extractTokenParamsHtml = `
`
const extractTokenExpectedToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkalVzZXJuYW1lIjoiYW5zZzE5MUB5YWhvby5jb20iLCJpZCI6IjAxZWFmNTE5LTA0OWItNGIyOS04ZjZhLWQyNjIyZjNiMWJjNiIsImdpdmVuX25hbWUiOiJBbnNodWwiLCJmYW1pbHlfbmFtZSI6Ikd1cHRhIiwibmFtZSI6IkFuc2h1bCBHdXB0YSIsImVtYWlsIjoiYW5zZzE5MUB5YWhvby5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYWNjb3VudF9pZCI6Ijk5NzI5Mzc0NDIxMiIsImRqaWQiOiIwMWVhZjUxOS0wNDliLTRiMjktOGY2YS1kMjYyMmYzYjFiYzYiLCJ0cmFja2lkIjoiMWM0NGQyMTRmM2VlYTZiMzcyNDYxNDc3NDc0NDMyODJmMTRmY2ZjYmI4NmE4NmVjYTI0MDc2ZDVlMzU4ZmUzZCIsInVwZGF0ZWRfYXQiOjE3MTI3OTQxNTYsImNyZWF0ZWRfYXQiOjE3MTI3OTQxNTYsInVhdCI6MTcyMjU1MjMzOSwicm9sZXMiOlsiQkFSUk9OUy1DSEFOR0VQQVNTV09SRCIsIkZSRUVSRUctQkFTRSIsIkZSRUVSRUctSU5ESVZJRFVBTCIsIldTSi1DSEFOR0VQQVNTV09SRCIsIldTSi1BUkNISVZFIiwiV1NKLVNFTEZTRVJWIiwiSUJELUlORElWSURVQUwiLCJJQkQtSUNBIiwiSUJELUFFSSJdLCJkalN0YXR1cyI6WyJJQkRfVVNFUlMiXSwicHJ0cyI6IjIwMjQwNDEwMTcwOTE2LTA0MDAiLCJjcmVhdGVUaW1lc3RhbXAiOiIyMDI0MDQxMTAwMDkxNloiLCJzdXVpZCI6Ik1ERmxZV1kxTVRrdE1EUTVZaTAwWWpJNUxUaG1ObUV0WkRJMk1qSm1NMkl4WW1NMi50S09fM014VkVReks3dE5qTkdxUXNZMlBNbXp5cUxGRkxySnBrZGhrcDZrIiwic3ViIjoiMDFlYWY1MTktMDQ5Yi00YjI5LThmNmEtZDI2MjJmM2IxYmM2IiwiYXVkIjoiR1NVMXBHMkJyZ2QzUHYyS0JuQVoyNHp2eTV1V1NDUW4iLCJpc3MiOiJodHRwczovL3Nzby5hY2NvdW50cy5kb3dqb25lcy5jb20vIiwiaWF0IjoxNzIyNTUyMzM5MTI0LCJleHAiOjE3MjI1NTI3NzExMjR9.HVn33IFttQrG1JKEV2oElIy3mm8TJ-3GpV_jqZE81_cY22z4IMWPz7zUGz0WgOoUuQGyrYXiaNrfxD6GaoimRL6wxrH0Fy5iYC3dOEdlGfldswfgEOwSiZkBJRc2wWTVQLm93EeJ5ZZyKIXGY_ZkwcYfhrwaTAz8McBBnRmZkm0eiNJQ5YK-QZL-yFa3DxMdPPW91jLA2rjOIVnJ-I_0nMwaJ4ZwXHG2Sw4aAXxtbFqIqarKwIdOUSpRFOCSYpeWcxmbliurKlP1djrKrYgYSZxsKOHZhnbikZDtoDCAlPRlfbKOO4u36KXooDYGJ6p__s2kGCLOLLkP_QLHMNU8Jg"
const extractTokenExpectedParams = "%7B%22response_type%22%3A%22code%22%2C%22client_id%22%3A%22GSU1pG2Brgd3Pv2KBnAZ24zvy5uWSCQn%22%2C%22redirect_uri%22%3A%22https%3A%2F%2Fmyibd.investors.com%2Foidc%2Fcallback%22%2C%22state%22%3A%22J-ihUYZIYzey682D.aOLszineC9qjPkM6Y6wWgFC61ABYBiuK9u48AHTFS5I%22%2C%22scope%22%3A%22openid%20idp_id%20roles%20email%20given_name%20family_name%20uuid%20djUsername%20djStatus%20trackid%20tags%20prts%20updated_at%20created_at%20offline_access%20djid%22%2C%22nonce%22%3A%22457bb517-f490-43b6-a55f-d93f90d698ad%22%7D"
func Test_extractTokenParams(t *testing.T) {
t.Parallel()
node, err := html.Parse(strings.NewReader(extractTokenParamsHtml))
require.NoError(t, err)
token, params, err := extractTokenParams(node)
require.NoError(t, err)
assert.Equal(t, extractTokenExpectedToken, token)
assert.Equal(t, extractTokenExpectedParams, params)
}
func TestClient_Authenticate(t *testing.T) {
t.Parallel()
expectedVal := "test-cookie"
expectedExp := time.Now().Add(time.Hour).Round(time.Second).In(time.UTC)
tp := httpmock.NewMockTransport()
tp.RegisterResponder("GET", signInUrl,
httpmock.NewStringResponder(http.StatusOK, extractAuthHtml))
tp.RegisterResponder("POST", authenticateUrl,
func(request *http.Request) (*http.Response, error) {
var body authRequestBody
require.NoError(t, json.NewDecoder(request.Body).Decode(&body))
assert.Equal(t, "abc", body.Username)
assert.Equal(t, "xyz", body.Password)
return httpmock.NewStringResponse(http.StatusOK, extractTokenParamsHtml), nil
})
tp.RegisterResponder("POST", postAuthUrl,
func(request *http.Request) (*http.Response, error) {
require.NoError(t, request.ParseForm())
assert.Equal(t, extractTokenExpectedToken, request.Form.Get("token"))
params, err := url.QueryUnescape(extractTokenExpectedParams)
require.NoError(t, err)
assert.Equal(t, params, request.Form.Get("params"))
resp := httpmock.NewStringResponse(http.StatusOK, "OK")
cookie := &http.Cookie{Name: cookieName, Value: expectedVal, Expires: expectedExp}
resp.Header.Set("Set-Cookie", cookie.String())
return resp, nil
})
client := NewClient(nil, nil, newTransport(tp))
cookie, err := client.Authenticate(context.Background(), "abc", "xyz")
require.NoError(t, err)
require.NotNil(t, cookie)
assert.Equal(t, expectedVal, cookie.Value)
assert.Equal(t, expectedExp, cookie.Expires)
}
func TestClient_Authenticate_401(t *testing.T) {
t.Parallel()
tp := httpmock.NewMockTransport()
tp.RegisterResponder("GET", signInUrl,
httpmock.NewStringResponder(http.StatusOK, extractAuthHtml))
tp.RegisterResponder("POST", authenticateUrl,
func(request *http.Request) (*http.Response, error) {
var body authRequestBody
require.NoError(t, json.NewDecoder(request.Body).Decode(&body))
assert.Equal(t, "abc", body.Username)
assert.Equal(t, "xyz", body.Password)
return httpmock.NewStringResponse(http.StatusUnauthorized, `{"name":"ValidationError","code":"ERR016","message":"Wrong username or password","description":"Wrong username or password"}`), nil
})
client := NewClient(nil, nil, newTransport(tp))
cookie, err := client.Authenticate(context.Background(), "abc", "xyz")
assert.Nil(t, cookie)
assert.ErrorIs(t, err, ErrBadCredentials)
}
type testReliableTransport http.Client
func newTransport(tp *httpmock.MockTransport) *testReliableTransport {
return (*testReliableTransport)(&http.Client{Transport: tp})
}
func (t *testReliableTransport) String() string {
return "testReliableTransport"
}
func (t *testReliableTransport) Do(req *http.Request) (*http.Response, error) {
return (*http.Client)(t).Do(req)
}
func (t *testReliableTransport) Properties() transport.Properties {
return transport.PropertiesFree | transport.PropertiesReliable
}