Skip to content

Commit

Permalink
papercut-fix: perform login on enter button press for basic auth (#434)
Browse files Browse the repository at this point in the history
Signed-off-by: Vishwas Rajashekar <[email protected]>
  • Loading branch information
vrajashkr authored Apr 2, 2024
1 parent 9de2337 commit c78b303
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 29 deletions.
152 changes: 141 additions & 11 deletions src/__tests__/LoginPage/SignIn.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ afterEach(() => {

describe('Signin component automatic navigation', () => {
it('navigates to homepage when user is already logged in', async () => {
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={true} setIsLoggedIn={() => {}} />);
render(<SignIn isLoggedIn={true} setIsLoggedIn={() => {}} />);
await expect(mockedUsedNavigate).toHaveBeenCalledWith('/home');
});

it('navigates to homepage when auth is disabled', async () => {
// mock request to check auth
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { http: {} } });
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);
await waitFor(() => {
expect(mockedUsedNavigate).toHaveBeenCalledWith('/home');
});
Expand All @@ -48,7 +48,7 @@ describe('Sign in form', () => {
});

it('should change username and password values on user input', async () => {
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);
const usernameInput = await screen.findByLabelText(/^Username/i);
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
fireEvent.change(usernameInput, { target: { value: 'test' } });
Expand All @@ -59,7 +59,7 @@ describe('Sign in form', () => {
});

it('should display error if username and password values are empty after change', async () => {
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);
const usernameInput = await screen.findByLabelText(/^Username/i);
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.click(usernameInput);
Expand All @@ -74,24 +74,154 @@ describe('Sign in form', () => {
await waitFor(() => expect(passwordError).toBeInTheDocument());
});

it('should log in the user and navigate to homepage if login is successful', async () => {
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
const submitButton = await screen.findByText('Continue');
it('should log in the user and navigate to homepage if login is successful using button', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const usernameInput = await screen.findByLabelText(/^Username/i);
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(usernameInput, 'test');
userEvent.type(passwordInput, 'test');

jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } });
const submitButton = await screen.findByText('Continue');
fireEvent.click(submitButton);
await waitFor(() => {
expect(mockedUsedNavigate).toHaveBeenCalledWith('/home');
});
});

it('should display an error if username is blank and login is attempted using button', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(passwordInput, 'test');
const submitButton = await screen.findByTestId('basic-auth-submit-btn');
fireEvent.click(submitButton);

await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument());
await waitFor(() => expect(screen.queryByText(/enter a password/i)).not.toBeInTheDocument());
await waitFor(() => {
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});

it('should display an error if password is blank and login is attempted using button', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const usernameInput = await screen.findByLabelText(/^Username/i);
userEvent.type(usernameInput, 'test');
const submitButton = await screen.findByTestId('basic-auth-submit-btn');
fireEvent.click(submitButton);

await waitFor(() => expect(screen.queryByText(/enter a username/i)).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument());
await waitFor(() => {
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});

it('should display an error if username and password are both blank and login is attempted using button', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const submitButton = await screen.findByTestId('basic-auth-submit-btn');
fireEvent.click(submitButton);

await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument());
await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument());
await waitFor(() => {
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});

it('should log in the user and navigate to homepage if login is successful using enter key on username field', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const usernameInput = await screen.findByLabelText(/^Username/i);
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(usernameInput, 'test');
userEvent.type(passwordInput, 'test');

jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } });
userEvent.type(usernameInput, '{enter}');
await waitFor(() => {
expect(mockedUsedNavigate).toHaveBeenCalledWith('/home');
});
});

it('should log in the user and navigate to homepage if login is successful using enter key on password field', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const usernameInput = await screen.findByLabelText(/^Username/i);
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(usernameInput, 'test');
userEvent.type(passwordInput, 'test');

jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: {} } });
userEvent.type(passwordInput, '{enter}');
await waitFor(() => {
expect(mockedUsedNavigate).toHaveBeenCalledWith('/home');
});
});

it('should display an error if username is blank and login is attempted using enter key', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(passwordInput, 'test');
userEvent.type(passwordInput, '{enter}');

await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument());
await waitFor(() => expect(screen.queryByText(/enter a password/i)).not.toBeInTheDocument());
await waitFor(() => {
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});

it('should display an error if password is blank and login is attempted using enter key', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const usernameInput = await screen.findByLabelText(/^Username/i);
userEvent.type(usernameInput, 'test');
userEvent.type(usernameInput, '{enter}');

await waitFor(() => expect(screen.queryByText(/enter a username/i)).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument());
await waitFor(() => {
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});

it('should display an error if username and password are both blank and login is attempted using enter key', async () => {
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(passwordInput, '{enter}');

await waitFor(() => expect(screen.queryByText(/enter a username/i)).toBeInTheDocument());
await waitFor(() => expect(screen.queryByText(/enter a password/i)).toBeInTheDocument());
await waitFor(() => {
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});

it('should should display login error if login not successful', async () => {
render(<SignIn isAuthEnabled={true} setIsAuthEnabled={() => {}} isLoggedIn={false} setIsLoggedIn={() => {}} />);
const submitButton = await screen.findByText('Continue');
render(<SignIn isLoggedIn={false} setIsLoggedIn={() => {}} />);

const usernameInput = await screen.findByLabelText(/^Username/i);
const passwordInput = await screen.findByLabelText(/^Enter Password/i);
userEvent.type(usernameInput, 'test');
userEvent.type(passwordInput, 'test');

jest.spyOn(api, 'get').mockRejectedValue({ status: 401, data: {} });

const submitButton = await screen.findByText('Continue');
fireEvent.click(submitButton);
const errorDisplay = await screen.findByText(/Authentication Failed/i);

await waitFor(() => {
expect(screen.queryByText(/Authentication Failed/i)).toBeInTheDocument();
});
await waitFor(() => {
expect(errorDisplay).toBeInTheDocument();
expect(mockedUsedNavigate).not.toHaveBeenCalled();
});
});
});
71 changes: 53 additions & 18 deletions src/components/Login/SignIn.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ const useStyles = makeStyles(() => ({
export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading = () => {} }) {
const [usernameError, setUsernameError] = useState(null);
const [passwordError, setPasswordError] = useState(null);
const [username, setUsername] = useState(null);
const [password, setPassword] = useState(null);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [requestProcessing, setRequestProcessing] = useState(false);
const [requestError, setRequestError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
Expand Down Expand Up @@ -228,13 +228,20 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
});
};

const handleClick = (event) => {
event.preventDefault();
if (Object.keys(authMethods).includes('htpasswd')) {
const handleBasicAuthSubmit = () => {
setRequestError(false);
const isUsernameValid = handleUsernameValidation(username);
const isPasswordValid = handlePasswordValidation(password);
if (Object.keys(authMethods).includes('htpasswd') && isUsernameValid && isPasswordValid) {
handleBasicAuth();
}
};

const handleClick = (event) => {
event.preventDefault();
handleBasicAuthSubmit();
};

const handleGuestClick = () => {
setRequestProcessing(false);
setRequestError(false);
Expand All @@ -251,35 +258,55 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
);
};

const handleUsernameValidation = (username) => {
let isValid = true;
if (username === '') {
setUsernameError('Please enter a username');
isValid = false;
} else {
setUsernameError(null);
}
return isValid;
};

const handlePasswordValidation = (password) => {
let isValid = true;
if (password === '') {
setPasswordError('Please enter a password');
isValid = false;
} else {
setPasswordError(null);
}
return isValid;
};

const handleChange = (event, type) => {
event.preventDefault();
setRequestError(false);

const val = event.target?.value;
const isEmpty = val === '';

switch (type) {
case 'username':
setUsername(val);
if (isEmpty) {
setUsernameError('Please enter a username');
} else {
setUsernameError(null);
}
handleUsernameValidation(val);
break;
case 'password':
setPassword(val);
if (isEmpty) {
setPasswordError('Please enter a password');
} else {
setPasswordError(null);
}
handlePasswordValidation(val);
break;
default:
break;
}
};

const handleLoginInputFieldKeyDown = (event) => {
const keyPressed = event.key;
if (keyPressed === 'Enter') {
handleBasicAuthSubmit();
}
};

const renderThirdPartyLoginMethods = () => {
let isGoogle = isObject(authMethods.openid?.providers?.google);
// let isGitlab = isObject(authMethods.openid?.providers?.gitlab);
Expand Down Expand Up @@ -315,7 +342,7 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
{Object.keys(authMethods).length > 1 &&
Object.keys(authMethods).includes('openid') &&
Object.keys(authMethods.openid.providers).length > 0 && (
<Divider className={classes.divider} data-testId="openid-divider">
<Divider className={classes.divider} data-testid="openid-divider">
or
</Divider>
)}
Expand All @@ -334,6 +361,7 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
onInput={(e) => handleChange(e, 'username')}
error={usernameError != null}
helperText={usernameError}
onKeyDown={(e) => handleLoginInputFieldKeyDown(e)}
/>
<TextField
margin="normal"
Expand All @@ -349,6 +377,7 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
onInput={(e) => handleChange(e, 'password')}
error={passwordError != null}
helperText={passwordError}
onKeyDown={(e) => handleLoginInputFieldKeyDown(e)}
/>
{requestProcessing && <CircularProgress style={{ marginTop: 20 }} color="secondary" />}
{requestError && (
Expand All @@ -357,7 +386,13 @@ export default function SignIn({ isLoggedIn, setIsLoggedIn, wrapperSetLoading =
</Alert>
)}
<div>
<Button fullWidth variant="contained" className={classes.continueButton} onClick={handleClick}>
<Button
fullWidth
variant="contained"
className={classes.continueButton}
onClick={handleClick}
data-testid="basic-auth-submit-btn"
>
Continue
</Button>
</div>
Expand Down

0 comments on commit c78b303

Please sign in to comment.