Skip to content

Commit

Permalink
Refactor and test oidc callback component
Browse files Browse the repository at this point in the history
  • Loading branch information
arbulu89 committed Aug 7, 2024
1 parent 3f0bb54 commit b0b6b39
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 16 deletions.
24 changes: 16 additions & 8 deletions assets/js/pages/Login/LoginSSO.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import React from 'react';

import Button from '@common/Button';

export default function LoginSSO({ singleSignOnUrl }) {
export default function LoginSSO({ singleSignOnUrl, error }) {
return (
<Button
onClick={() => {
window.location.href = singleSignOnUrl;
}}
>
Login with Single Sign-on
</Button>
<>
{error && (
<span className="block text-sm font-medium mb-5">
An error occurred while trying to Login. Please retry login again.
Should the error persist, contact the administrator.
</span>
)}
<Button
onClick={() => {
window.location.href = singleSignOnUrl;
}}
>
Login with Single Sign-on
</Button>
</>
);
}
51 changes: 43 additions & 8 deletions assets/js/pages/OidcCallback/OidcCallback.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React, { useEffect, useState } from 'react';
import TrentoLogo from '@static/trento-dark.svg';
import { useLocation, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { performOidcEnrollment } from '@state/user';
import { getSingleSignOnLoginUrl } from '@lib/auth/config';
import LoginSSO from '@pages/Login/LoginSSO';
import { getUserProfile } from '@state/selectors/user';

function OidCallback() {
const { search } = useLocation();
const dispatch = useDispatch();
const navigate = useNavigate();
const { authError, authInProgress, loggedIn } = useSelector(
(state) => state.user
);
const [error, setError] = useState(null);
const { authError, loggedIn } = useSelector(getUserProfile);

useEffect(() => {
const params = new URLSearchParams(search);

// handle different result
const code = params.get('code');
const state = params.get('state');

console.log('code', params.get('code'));
console.log('state', params.get('state'));
setError(!code || !state);

dispatch(performOidcEnrollment({ state, code }));
}, [search]);
Expand All @@ -30,7 +30,42 @@ function OidCallback() {
}
}, [loggedIn]);

return <div>oidc callback</div>;
if (authError || error) {
return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto rounded"
src={TrentoLogo}
alt="Trento"
/>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Login Failed
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<LoginSSO singleSignOnUrl={getSingleSignOnLoginUrl()} error />
</div>
</div>
</div>
);
}

return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto rounded"
src={TrentoLogo}
alt="Trento"
/>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Loading...
</h2>
</div>
</div>
);
}

export default OidCallback;
95 changes: 95 additions & 0 deletions assets/js/pages/OidcCallback/OidcCallback.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { screen } from '@testing-library/react';
import 'intersection-observer';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event';
import { withState, renderWithRouterMatch } from '@lib/test-utils';
import OidcCallback from './OidcCallback';

describe('OidcCallback component', () => {
beforeEach(() => {
jest.resetModules();
});

it('should display loading state when authentication is happening', async () => {
const [StatefulOidCallback, store] = withState(<OidcCallback />, {
user: {},
});

renderWithRouterMatch(StatefulOidCallback, {
path: 'auth/oidc_callback',
route: `/auth/oidc_callback?code=code&state=state`,
});

expect(screen.getByText('Loading...')).toBeVisible();

expect(store.getActions()).toContainEqual({
type: 'PERFORM_OIDC_ENROLLMENT',
payload: { code: 'code', state: 'state' },
});
});

it('should display an error message if some search param is missing', async () => {
const user = userEvent.setup();

const [StatefulOidCallback] = withState(<OidcCallback />, {
user: {},
});

renderWithRouterMatch(StatefulOidCallback, {
path: 'auth/oidc_callback',
route: `/auth/oidc_callback?code=code`,
});

expect(screen.getByText('Login Failed')).toBeVisible();

expect(
screen.getByText('An error occurred while trying to Login', {
exact: false,
})
).toBeVisible();

const loginButton = screen.getByRole('button', {
name: 'Login with Single Sign-on',
});
user.click(loginButton);

expect(window.location.pathname).toBe('/auth/oidc_callback');
});

it('should display an error message if authentication fails', async () => {
const [StatefulOidCallback] = withState(<OidcCallback />, {
user: {
authError: true,
},
});

renderWithRouterMatch(StatefulOidCallback, {
path: 'auth/oidc_callback',
route: `/auth/oidc_callback?code=code&state=state`,
});

expect(screen.getByText('Login Failed')).toBeVisible();

expect(
screen.getByText('An error occurred while trying to Login', {
exact: false,
})
).toBeVisible();
});

it('should navigate to home after user is logged in', () => {
const [StatefulOidCallback] = withState(<OidcCallback />, {
user: {
loggedIn: true,
},
});

renderWithRouterMatch(StatefulOidCallback, {
path: '',
route: '/',
});

expect(window.location.pathname).toBe('/');
});
});

0 comments on commit b0b6b39

Please sign in to comment.