twoFactorSetup.test.tsx2.4 KBView on GitHub
/**
 * AuthenticatorSetupPanel — the enrollment step encodes the `otpauth://` URI as a scannable
 * QR, and keeps the typed setup key reachable behind a disclosure for the screens that have
 * no camera pointed at them.
 */
import { AuthenticatorSetupPanel } from '@/modules/auth/components/two-factor-setup';
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';

const TOTP_URI =
  'otpauth://totp/Cedar:rep%40example.com?secret=[redacted];
const SETUP_KEY=[redacted];

describe('AuthenticatorSetupPanel', () => {
  it('renders the QR from the full otpauth URI, not the bare secret', () => {
    render(<AuthenticatorSetupPanel totpURI={TOTP_URI} setupKey=[redacted] />);

    // The accessible title is the only stable handle on the generated SVG.
    const qr = screen.getByTitle('Two-factor authentication setup code');
    expect(qr).toBeTruthy();

    // A QR of the bare secret scans into an unlabelled, wrongly-configured account, so prove
    // the encoder was handed the URI: its module count is driven by the payload length, and
    // the two payloads do not produce the same grid.
    const svg = qr.closest('svg')!;
    expect(svg.getAttribute('viewBox')).toBe(qrViewBox(TOTP_URI));
    expect(svg.getAttribute('viewBox')).not.toBe(qrViewBox(SETUP_KEY));
  });

  it('hides the setup key until asked, then shows it', () => {
    render(<AuthenticatorSetupPanel totpURI={TOTP_URI} setupKey=[redacted] />);

    expect(screen.queryByText(SETUP_KEY)).toBeNull();

    fireEvent.click(screen.getByRole('button', { name: /enter a setup key instead/i }));

    expect(screen.getByText(SETUP_KEY)).toBeTruthy();
  });

  it('falls back to the whole URI when the secret could not be parsed out', () => {
    render(<AuthenticatorSetupPanel totpURI={TOTP_URI} setupKey=[redacted] />);

    fireEvent.click(screen.getByRole('button', { name: /enter a setup key instead/i }));

    expect(screen.getByText(TOTP_URI)).toBeTruthy();
  });
});

/** The viewBox a freshly-rendered QR of `value` carries, used as a payload fingerprint. */
function qrViewBox(value: string): string {
  const { unmount } = render(<AuthenticatorSetupPanel totpURI={value} setupKey=[redacted] />);
  const box = screen
    .getAllByTitle('Two-factor authentication setup code')
    .at(-1)!
    .closest('svg')!
    .getAttribute('viewBox')!;
  unmount();
  return box;
}