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 }