Files
JQC_multi_tenant/tests/test_panel_mfa.py
T
2026-07-04 13:40:03 -04:00

56 lines
1.8 KiB
Python

"""
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('<?xml')
assert '<svg' in svg