Files
openvpncertupdate/tests/test_widgets.py
Vlad Doloman 4798ce3f18 fix TUI crash on serial consoles; make index.txt the sole email store
curses.use_default_colors() raises on terminals (e.g. serial consoles
using TERM=linux/vt100) whose terminfo lacks default-color support;
init_colors() now falls back to an explicit black background and caps
the "disabled" color to 8-color terminals.

--reissue without --email silently sent no mail when the CN wasn't in
openvpncertupdate-metadata.json, even though its email was already
embedded in the cert's index.txt subject (via EASYRSA_REQ_EMAIL) and
the TUI already fell back to it. Since build_client_full() always
writes that subject field whenever an email is known, metadata.json
was a redundant, driftable copy — remove it and have get_email() read
index.txt directly, picking the last non-revoked entry for a CN since
index.txt can contain revoked/duplicate lines for the same CN.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 15:46:10 +03:00

85 lines
2.7 KiB
Python

from unittest.mock import MagicMock, patch
import curses as _curses
# Stub just enough of the curses API for InputField to import
_curses.color_pair = lambda x: 0
_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
from openvpncertupdate import InputField, clamp, init_colors, COLOR_NORMAL, COLOR_DISABLED
def _field(initial="", mask=False):
win = MagicMock()
win.getmaxyx.return_value = (1, 80)
return InputField(win, 0, 0, 40, initial=initial, mask=mask)
def test_initial_value():
assert _field("hello").value == "hello"
def test_backspace_removes_last():
f = _field("hello")
f.handle_key(_curses.KEY_BACKSPACE)
assert f.value == "hell"
def test_type_appends():
f = _field("hi")
f.handle_key(ord("!"))
assert f.value == "hi!"
def test_left_then_insert():
f = _field("ac")
f.handle_key(_curses.KEY_LEFT)
f.handle_key(_curses.KEY_LEFT)
f.handle_key(ord("b"))
assert f.value == "bac"
def test_delete_key():
f = _field("abc")
f.handle_key(_curses.KEY_HOME)
f.handle_key(_curses.KEY_DC)
assert f.value == "bc"
def test_clamp_bounds():
assert clamp(5, 0, 10) == 5
assert clamp(-1, 0, 10) == 0
assert clamp(11, 0, 10) == 10
def _init_colors_calls(*, use_default_colors_raises, colors):
calls = []
use_default = MagicMock(
side_effect=_curses.error("use_default_colors() returned ERR")
if use_default_colors_raises else None
)
with patch.object(_curses, "start_color"), \
patch.object(_curses, "use_default_colors", use_default), \
patch.object(_curses, "init_pair", side_effect=lambda *a: calls.append(a)), \
patch.object(_curses, "COLORS", colors, create=True):
init_colors()
return calls
def test_init_colors_uses_default_colors_when_supported():
calls = _init_colors_calls(use_default_colors_raises=False, colors=256)
normal = next(c for c in calls if c[0] == COLOR_NORMAL)
disabled = next(c for c in calls if c[0] == COLOR_DISABLED)
assert normal[2] == -1
assert disabled[1:] == (8, -1)
def test_init_colors_falls_back_on_serial_console():
# Serial consoles (e.g. TERM=linux/vt100) often lack the terminfo
# "op"/"AX" capability, so use_default_colors() raises curses.error
# and only 8 colors are available.
calls = _init_colors_calls(use_default_colors_raises=True, colors=8)
normal = next(c for c in calls if c[0] == COLOR_NORMAL)
disabled = next(c for c in calls if c[0] == COLOR_DISABLED)
assert normal[2] == _curses.COLOR_BLACK
assert disabled[1:] == (_curses.COLOR_WHITE, _curses.COLOR_BLACK)