""" tests/test_panel_mfa.py ----------------------- Behaviour tests for the superadmin-panel two-factor helper (control/mfa.py). The panel routes are thin wrappers over this module (and mirror app/utils/mfa.py, which is covered by tests/test_mfa.py). These tests lock the panel helper's crypto behaviour independently so the control-plane mirror can't silently drift. Pure logic — no control DB, no MySQL, no Flask app required. """ import pyotp def test_totp_round_trip_and_rejection(): from control import mfa s = mfa.new_secret() assert mfa.verify_totp(s, pyotp.TOTP(s).now()) is True assert mfa.verify_totp(s, '000000') is False assert mfa.verify_totp(s, 'abc') is False assert mfa.verify_totp('', '123456') is False assert mfa.verify_totp(s, None) is False def test_recovery_codes_are_hashed_single_use(): from control import mfa plaintext, hashed = mfa.generate_recovery_codes() assert len(plaintext) == len(hashed) == 10 # Never stored in plaintext. assert all(p not in hashed for p in plaintext) matched, remaining = mfa.check_and_consume_recovery(hashed, plaintext[3]) assert matched is True assert len(remaining) == 9 # Consumed code cannot be reused. reused, _ = mfa.check_and_consume_recovery(remaining, plaintext[3]) assert reused is False # A different, unused code still works. ok, remaining2 = mfa.check_and_consume_recovery(remaining, plaintext[0]) assert ok is True assert len(remaining2) == 8 def test_provisioning_uri_and_qr_are_well_formed(): from control import mfa s = mfa.new_secret() uri = mfa.provisioning_uri(s, 'admin@example.com') assert uri.startswith('otpauth://totp/') assert 'JQC%20Admin' in uri or 'JQC Admin' in uri svg = mfa.qr_svg(uri) assert svg.lstrip().startswith('