Files
openvpncertupdate/tests/test_dialogs.py
Vlad Doloman 4af7d67bf3 Strengthen test_show_cert_form_confirm to assert result.days
A blank/default days value round-trips as "" even from a reverted
three-field form (CertFormResult.days defaults to ""), so it would not
prove the Days field was traversed. Use a non-blank days="90" instead,
which only survives if the field exists and is validated by _submit().
2026-08-15 05:34:13 +03:00

226 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for TUI DIALOGS (show_confirm, show_cert_form).
Uses the same mock-curses approach as test_widgets.py — no real terminal needed.
"""
from unittest.mock import MagicMock, patch
import curses as _curses
# Stub curses constants before importing the module under test, mirroring
# what test_widgets.py does for InputField's dependencies.
_curses.color_pair = lambda x: 0
_curses.curs_set = lambda x: None
_curses.A_UNDERLINE = 0
_curses.A_BOLD = 0
_curses.KEY_BACKSPACE = 263
_curses.KEY_LEFT = 260
_curses.KEY_RIGHT = 261
_curses.KEY_HOME = 262
_curses.KEY_END = 360
_curses.KEY_DC = 330
_curses.KEY_BTAB = 353
_curses.KEY_F5 = 269
_curses.KEY_ENTER = 343
from openvpncertupdate import show_confirm, show_cert_form, CertFormResult
def _make_stdscr(rows=40, cols=120):
"""Return a minimal MagicMock that looks like a curses stdscr."""
stdscr = MagicMock()
stdscr.getmaxyx.return_value = (rows, cols)
return stdscr
def _make_win(rows=20, cols=60):
"""Return a minimal MagicMock for the subwindow created by curses.newwin."""
win = MagicMock()
win.getmaxyx.return_value = (rows, cols)
return win
# ---------------------------------------------------------------------------
# show_confirm tests
# ---------------------------------------------------------------------------
def test_show_confirm_yes():
"""Pressing 'y' returns True."""
stdscr = _make_stdscr()
win = _make_win()
win.getch.side_effect = [ord("y")]
with patch("curses.newwin", return_value=win):
result = show_confirm(stdscr, "Do you want to continue?")
assert result is True
def test_show_confirm_esc():
"""Pressing Escape (27) returns False."""
stdscr = _make_stdscr()
win = _make_win()
win.getch.side_effect = [27]
with patch("curses.newwin", return_value=win):
result = show_confirm(stdscr, "Do you want to continue?")
assert result is False
def test_show_confirm_clamps_width_to_screen_for_long_line():
"""A long message line (e.g. a Cryptgeon password URL) must not make the
requested newwin() width exceed the screen — that's what made
curses.newwin() raise 'curses function returned NULL' on a narrow
terminal such as a serial console."""
stdscr = _make_stdscr(rows=24, cols=80)
win = _make_win()
win.getch.side_effect = [ord("y")]
long_url = "https://cryptgeon.example.com/note/" + "a" * 100 + "#" + "b" * 64
with patch("curses.newwin", return_value=win) as mock_newwin:
show_confirm(stdscr, f"Send email?\n\nPassword URL: {long_url}")
h, w, y, x = mock_newwin.call_args.args
assert w <= 80
assert x >= 0
def test_show_confirm_newwin_failure_returns_false():
"""If curses.newwin() still fails despite clamping, show_confirm must
degrade to 'declined' rather than crash the whole app."""
stdscr = _make_stdscr()
with patch("curses.newwin", side_effect=_curses.error("returned NULL")):
result = show_confirm(stdscr, "Do you want to continue?")
assert result is False
# ---------------------------------------------------------------------------
# show_cert_form tests
# ---------------------------------------------------------------------------
def test_show_cert_form_cancel():
"""Pressing Escape returns a CertFormResult with confirmed=False."""
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [27]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="alice", email="alice@example.com")
assert isinstance(result, CertFormResult)
assert result.confirmed is False
def test_show_cert_form_uses_erase_not_clear():
# clear() forces a full-screen repaint on every refresh(); the form
# redraws on every keystroke, so it must use erase() instead.
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [9, 27] # Tab, then Escape
with patch("curses.newwin", return_value=win):
show_cert_form(stdscr, cn="alice", email="alice@example.com")
assert win.erase.called
assert not win.clear.called
def test_show_cert_form_confirm():
"""Enter advances through the 4 fields to the Continue button; Enter on
Continue confirms. That's 5 Enter keypresses total.
days="90" (non-blank) is used deliberately: a blank default would still
read back as "" even from a reverted three-field form (CertFormResult.days
defaults to ""), so it would not actually prove the days field was
traversed. A non-blank value only round-trips if the days field exists,
was reached by the Enter sequence, and was carried into the result."""
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [10, 10, 10, 10, 10]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="bob@example.com", days="90")
assert isinstance(result, CertFormResult)
assert result.confirmed is True
assert result.cn == "bob"
assert result.email == "bob@example.com"
assert result.days == "90"
assert len(result.password) > 0
def test_show_cert_form_cancel_button():
"""Tab to the Cancel button (5 Tabs from start) and press Enter cancels."""
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
# Tab×5: cn→email→days→password→Continue→Cancel, then Enter
win.getch.side_effect = [9, 9, 9, 9, 9, 10]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="alice", email="alice@example.com")
assert isinstance(result, CertFormResult)
assert result.confirmed is False
def test_show_cert_form_clamps_to_narrow_screen():
"""The form's fixed 15x62 size must not exceed a smaller-than-usual
screen, which would make curses.newwin() raise."""
stdscr = _make_stdscr(rows=10, cols=40)
win = _make_win()
win.getch.side_effect = [27]
with patch("curses.newwin", return_value=win) as mock_newwin:
show_cert_form(stdscr, cn="alice", email="alice@example.com")
h, w, y, x = mock_newwin.call_args.args
assert h <= 10
assert w <= 40
assert y >= 0
assert x >= 0
def test_show_cert_form_returns_days():
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [10, 10, 10, 10, 10]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="b@c.d", days="90")
assert result.confirmed is True
assert result.days == "90"
def test_show_cert_form_blank_days_means_inherit():
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [10, 10, 10, 10, 10]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="b@c.d", days="")
assert result.confirmed is True
assert result.days == ""
def test_show_cert_form_days_field_is_digits_only():
# cn is read-only here, so focus starts on email: Tab once to reach days.
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [9] + [ord(c) for c in "9a0!"] + [10, 10, 10]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="b@c.d", days="",
cn_readonly=True)
assert result.confirmed is True
assert result.days == "90"
def test_show_cert_form_refuses_zero_days():
# Submitting "0" must keep the form open rather than closing and failing
# later inside EasyRSA. Enter×5 tries to submit, Esc then cancels.
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [10, 10, 10, 10, 10, 27]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="b@c.d", days="0")
assert result.confirmed is False
def test_show_cert_form_normalises_leading_zero_days():
stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70)
win.getch.side_effect = [10, 10, 10, 10, 10]
with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="b@c.d", days="090")
assert result.days == "90"
def test_show_cert_form_newwin_failure_returns_cancelled():
"""If curses.newwin() still fails despite clamping, show_cert_form must
degrade to an unconfirmed result rather than crash the whole app."""
stdscr = _make_stdscr()
with patch("curses.newwin", side_effect=_curses.error("returned NULL")):
result = show_cert_form(stdscr, cn="alice", email="alice@example.com")
assert isinstance(result, CertFormResult)
assert result.confirmed is False