Flag guests that no backup job covers

The guest table marks with "!" each guest that Proxmox's not-backed-up
list returns. The frame's bottom border shows the count, e.g.
"mainframe · 2 without backup job". This uses Proxmox's own answer, so
it also counts jobs that select guests by pool.

If the coverage call fails, the guests are still listed and a warning
says the coverage is unknown.

Closes #9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #20.
This commit is contained in:
DuckbootandClaude Opus 5.5 committed 2026-11-02 23:02:35 +01:00
1 parent 5a3ca5a216
commit 520de16cc6
5 files changed
+97 -6

No files matched your search

+43
View File
@@ -49,6 +49,7 @@ class Fake:
self.use_error = None
self.use_hook = None
self.guests_by_context = {}
self.unprotected, self.unprotected_error = set(), None
self.list_hook = None
# delivery A (guest info screen)
self.net_configs = {} # vmid -> extra config keys (netN, ipconfigN)
@@ -103,6 +104,11 @@ class Fake:
self.status_calls.append(guest["vmid"])
return self.live_status.get(guest["vmid"], guest["status"])
def not_backed_up(self):
if self.unprotected_error:
raise self.unprotected_error
return set(self.unprotected)
def contexts(self):
self.contexts_calls += 1
if self.contexts_error:
@@ -195,6 +201,7 @@ def fake(monkeypatch):
"backup_now",
"bridges",
"set_net",
"not_backed_up",
):
monkeypatch.setattr(cli, name, getattr(f, name), raising=False)
for name in ("list_guests", "guest_config", "disks", "action", "set_resources",
@@ -2108,3 +2115,39 @@ async def test_info_resources_reload_after_edit(fake, monkeypatch):
await settle(app, pilot)
assert isinstance(app.screen, appmod.InfoScreen)
assert resource_rows(app)["CPU"].startswith("8 cores")
# --- #9: flag guests that no backup job covers --------------------------------------
def first_cell(app, vmid):
return str(app.query_one("#guests", DataTable).get_row(str(vmid))[0])
async def test_unprotected_guests_are_marked_and_counted(fake):
fake.unprotected = {100}
app = make_app()
async with app.run_test(size=SIZE) as pilot:
await settle(app, pilot)
assert first_cell(app, 100) == "!"
assert first_cell(app, 108) == ""
subtitle = str(app.query_one("#guests_section").border_subtitle)
assert "mainframe" in subtitle and "1 without backup job" in subtitle
async def test_no_count_when_every_guest_is_covered(fake):
app = make_app()
async with app.run_test(size=SIZE) as pilot:
await settle(app, pilot)
assert str(app.query_one("#guests_section").border_subtitle) == "mainframe"
assert first_cell(app, 100) == "" and first_cell(app, 108) == ""
async def test_backup_coverage_failure_still_lists_guests(fake):
fake.unprotected_error = cli.CliError("Error: boom")
app = make_app()
async with app.run_test(size=SIZE) as pilot:
await settle(app, pilot)
assert app.query_one("#guests", DataTable).row_count == 2
assert any(sev == "warning" for _, sev in app.seen) # coverage unknown, said so
assert app.is_running