CloudLoginCaptcha.test.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. /**
  2. * Bambu Cloud sign-in when Bambu is challenging the network with a CAPTCHA (#2790).
  3. *
  4. * The backend answers `reason: 'captcha'`, meaning no credential will be
  5. * accepted until the challenge clears and there is nothing in Bambuddy that can
  6. * answer it. A toast is the wrong shape for that: it names a problem the user
  7. * cannot act on and then disappears. The reporter saw Bambu's own sentence,
  8. * "We need you to confirm you are not a robot", flash by with no challenge
  9. * behind it and filed it as a bug.
  10. */
  11. import { describe, it, expect } from 'vitest';
  12. import { screen, waitFor, within } from '@testing-library/react';
  13. import userEvent from '@testing-library/user-event';
  14. import { useTranslation } from 'react-i18next';
  15. import { http, HttpResponse } from 'msw';
  16. import { render } from '../utils';
  17. import { LoginForm } from '../../pages/ProfilesPage';
  18. import { server } from '../mocks/server';
  19. function Harness() {
  20. const { t } = useTranslation();
  21. return <LoginForm onSuccess={() => {}} t={t} />;
  22. }
  23. async function submitCredentials() {
  24. const user = userEvent.setup();
  25. await user.type(await screen.findByPlaceholderText('your@email.com'), 'user@example.com');
  26. await user.type(screen.getByPlaceholderText('••••••••'), 'hunter2');
  27. await user.click(screen.getByRole('button', { name: /login/i }));
  28. return user;
  29. }
  30. describe('Bambu Cloud sign-in blocked by a CAPTCHA', () => {
  31. it('explains the challenge in place and offers the token route', async () => {
  32. server.use(
  33. http.post('/api/v1/cloud/login', () =>
  34. HttpResponse.json({
  35. success: false,
  36. needs_verification: false,
  37. reason: 'captcha',
  38. message: 'We need you to confirm you are not a robot',
  39. }),
  40. ),
  41. );
  42. render(<Harness />);
  43. const user = await submitCredentials();
  44. const panel = await screen.findByRole('alert');
  45. expect(panel).toHaveTextContent(/Bambu Cloud is asking for a CAPTCHA/i);
  46. // The two things the reporter had no way to find out.
  47. expect(panel).toHaveTextContent(/email and password are not the problem/i);
  48. expect(panel).toHaveTextContent(/clears by itself within a few hours/i);
  49. // Bambu's raw sentence is never what the user is left holding.
  50. expect(screen.queryByText('We need you to confirm you are not a robot')).not.toBeInTheDocument();
  51. // The one action that does work is one click away.
  52. await user.click(within(panel).getByRole('button', { name: /use access token instead/i }));
  53. expect(await screen.findByPlaceholderText('eyJ...')).toBeInTheDocument();
  54. expect(screen.queryByText(/Bambu Cloud is asking for a CAPTCHA/i)).not.toBeInTheDocument();
  55. });
  56. it('leaves an ordinary rejection as a toast', async () => {
  57. server.use(
  58. http.post('/api/v1/cloud/login', () =>
  59. HttpResponse.json({ success: false, needs_verification: false, message: 'Login failed' }),
  60. ),
  61. );
  62. render(<Harness />);
  63. await submitCredentials();
  64. await waitFor(() => expect(screen.getByText('Login failed')).toBeInTheDocument());
  65. expect(screen.queryByText(/Bambu Cloud is asking for a CAPTCHA/i)).not.toBeInTheDocument();
  66. });
  67. });