Track the superpowers planning documents under docs/superpowers/plans/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
136 lines
5.9 KiB
Markdown
136 lines
5.9 KiB
Markdown
# TUI Main Menu Email Column Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Show each cert's email address alongside its CN in the TUI main menu's cert list rows.
|
|
|
|
**Architecture:** `CertInfo.email` is already populated by `load_expiring_certs()`/`load_all_certs()` from `index.txt` (`_parse_index_line()`), and `show_main_screen()` already receives the full `certs: list[CertInfo]`. This is a pure rendering change: extend the existing row-format string (`openvpncertupdate.py`, inside `show_main_screen()`'s render loop) to append the email, padding the CN column to a fixed width so columns stay aligned. No new data plumbing.
|
|
|
|
**Tech Stack:** Python 3.9 stdlib `curses`. Tests via `pytest` + `unittest.mock`, matching `tests/test_main_screen.py`'s existing conventions (mocked `stdscr`, inspecting `stdscr.addstr.call_args_list`).
|
|
|
|
## Global Constraints
|
|
|
|
- Single-file script: change stays in `openvpncertupdate.py`, no new modules.
|
|
- Applies to both the expiring-only and "all certs" (`A`-toggle) views — they share this same render loop, so no separate handling needed.
|
|
- Unknown email renders as blank (not a placeholder string like `(none)`) — matches the TUI's existing minimalist row style; this is a deliberate difference from the `--list`/`--list-all` CLI table, which does use `(none)`.
|
|
- CN column gets a fixed width of 20 characters (padding with spaces), consistent with the existing fixed 18-char width already used for the expiry label column on the same line.
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
- **Modify `openvpncertupdate.py`:** one line inside `show_main_screen()`'s render loop (currently `line = f" {box} {lbl:<18} {cert.cn}"`, around line 941 — find it via `_expiry_label(cert)` immediately above it).
|
|
- **Modify `tests/test_main_screen.py`:** extend the `_cert()` test helper to accept an optional `email` argument, and add two new tests.
|
|
|
|
---
|
|
|
|
### Task 1: Add email column to the cert list row
|
|
|
|
**Files:**
|
|
- Modify: `openvpncertupdate.py` (inside `show_main_screen()`, the `if is_cert(i):` branch of the render loop)
|
|
- Modify: `tests/test_main_screen.py` (`_cert()` helper + new tests)
|
|
|
|
**Interfaces:**
|
|
- Consumes: `CertInfo.email: str` (already exists, default `""`), `CertInfo.cn: str`.
|
|
- Produces: nothing new — this is a leaf rendering change with no other task depending on it.
|
|
|
|
- [ ] **Step 1: Extend the `_cert()` test helper**
|
|
|
|
In `tests/test_main_screen.py`, replace:
|
|
|
|
```python
|
|
def _cert(cn: str, days_left: int) -> CertInfo:
|
|
"""Build a minimal CertInfo; expires value is a plausible UTC datetime."""
|
|
return CertInfo(
|
|
cn=cn,
|
|
expires=datetime(2030, 1, 1, tzinfo=timezone.utc),
|
|
days_left=days_left,
|
|
)
|
|
```
|
|
|
|
with:
|
|
|
|
```python
|
|
def _cert(cn: str, days_left: int, email: str = "") -> CertInfo:
|
|
"""Build a minimal CertInfo; expires value is a plausible UTC datetime."""
|
|
return CertInfo(
|
|
cn=cn,
|
|
expires=datetime(2030, 1, 1, tzinfo=timezone.utc),
|
|
days_left=days_left,
|
|
email=email,
|
|
)
|
|
```
|
|
|
|
- [ ] **Step 2: Write the failing tests**
|
|
|
|
In `tests/test_main_screen.py`, add directly after `test_cursor_row_has_reverse_attribute` (search for that function name):
|
|
|
|
```python
|
|
def test_cert_row_shows_email():
|
|
certs = [_cert("alice", 10, email="alice@example.com"), _cert("bob", 3)]
|
|
stdscr = _make_stdscr()
|
|
stdscr.getch.side_effect = [ord("q")]
|
|
with patch("openvpncertupdate.init_colors"):
|
|
show_main_screen(stdscr, certs)
|
|
alice_calls = [c for c in stdscr.addstr.call_args_list if "alice" in str(c.args[2])]
|
|
assert alice_calls
|
|
assert "alice@example.com" in alice_calls[0].args[2]
|
|
|
|
|
|
def test_cert_row_blank_when_no_email():
|
|
certs = [_cert("bob", 3)] # email defaults to ""
|
|
stdscr = _make_stdscr()
|
|
stdscr.getch.side_effect = [ord("q")]
|
|
with patch("openvpncertupdate.init_colors"):
|
|
show_main_screen(stdscr, certs)
|
|
bob_calls = [c for c in stdscr.addstr.call_args_list if "bob" in str(c.args[2])]
|
|
assert bob_calls
|
|
assert "(none)" not in bob_calls[0].args[2]
|
|
```
|
|
|
|
- [ ] **Step 3: Run tests to verify they fail**
|
|
|
|
Run: `python3 -m pytest tests/test_main_screen.py -k cert_row_shows_email -v`
|
|
Expected: FAIL — `assert "alice@example.com" in alice_calls[0].args[2]` fails because the row doesn't include the email yet (`test_cert_row_blank_when_no_email` will already pass since it only checks for absence of `"(none)"`, which is fine — it's asserting current behavior stays true after the change too, so it's not required to fail here, but run both to confirm the shows_email one fails)
|
|
|
|
- [ ] **Step 4: Update the row format**
|
|
|
|
In `openvpncertupdate.py`, inside `show_main_screen()`, find:
|
|
|
|
```python
|
|
if is_cert(i):
|
|
cert = certs[i]
|
|
box = "[X]" if cert.cn in checked else "[ ]"
|
|
lbl = _expiry_label(cert)
|
|
line = f" {box} {lbl:<18} {cert.cn}"
|
|
```
|
|
|
|
Replace the `line = ...` assignment with:
|
|
|
|
```python
|
|
line = f" {box} {lbl:<18} {cert.cn:<20} {cert.email}"
|
|
```
|
|
|
|
- [ ] **Step 5: Run tests to verify they pass**
|
|
|
|
Run: `python3 -m pytest tests/test_main_screen.py -v`
|
|
Expected: all tests in the file PASS (this also re-verifies no regressions in the pre-existing tests, since the row content assertions there — e.g. `test_cursor_row_has_reverse_attribute` — check for `"bob"`/`"alice"` substrings, not exact row content, so the added columns don't break them)
|
|
|
|
- [ ] **Step 6: Run the full suite**
|
|
|
|
Run: `python3 -m pytest tests/ -v`
|
|
Expected: all tests PASS, no regressions in other files
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add openvpncertupdate.py tests/test_main_screen.py
|
|
git commit -m "show email alongside CN in TUI main menu cert list"
|
|
```
|
|
|
|
---
|
|
|
|
## Final Verification
|
|
|
|
- [ ] Run `python3 -m pytest tests/ -v` — all tests pass, count should be 141 (prior) + 2 new = 143.
|