Files
gandalf/tests/test_ticket_text.py
jaredandClaude Opus 5.5 437f7a415f
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 1m26s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m30s
Lint / Python (flake8) (pull_request) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 1m4s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m28s
Test / Python Tests (pytest) (pull_request) Successful in 2m0s
Add recovery notes and fuller context to tickets
When an alert that opened a ticket clears, the monitor now comments on the
ticket (when it cleared, how long it lasted, failed checks, a link back).
It never closes or changes a ticket's status. Failed notes retry each
cycle for 24 hours; a 401/403 from Tinker stops them until restart.

Tickets are now Markdown with a "What to check" list and, where known:
the switch port the device plugs into (kept from the last time it was
seen, so a dead host still shows it), cutsheet cables and their path,
how often the alert fired in the last 7/30 days, other open alerts on the
device, and links into Gandalf (gandalf.public_url). Titles are unchanged
so Tinker's duplicate detection still works.

network_events gains recovery_notified, added at monitor startup; rows
that cleared before the upgrade count as already notified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:44:50 -05:00

65 lines
2.7 KiB
Python

"""Tests for ticket_text.py: the Markdown Gandalf puts in tickets."""
import os
import sys
from datetime import datetime, timedelta
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
import ticket_text as tt # noqa: E402
NOW = datetime(2026, 11, 10, 18, 0)
def test_duration():
assert tt.duration(42) == '42 s'
assert tt.duration(600) == '10 min'
assert tt.duration(3600) == '1 h'
assert tt.duration(3 * 3600 + 120) == '3 h 2 min'
assert tt.duration(3 * 86400 + 7200) == '3 d 2 h'
def test_speed():
assert tt.speed(100) == '100 Mbps'
assert tt.speed(2500) == '2.5 Gbps'
assert tt.speed(10000) == '10 Gbps'
assert tt.speed(0) == ''
def test_device_url_escapes_names():
assert tt.device_url('https://g', 'USW Flex Mini', 3) == 'https://g/device/USW%20Flex%20Mini#port-3'
assert tt.device_url('', 'x') == ''
def test_port_text():
assert tt.port_text('sw', 'Port 4', {'port_idx': 4, 'up': True, 'speed_mbps': 1000, 'poe_power': 4.21}) == \
'sw port 4: up at 1 Gbps, PoE 4.2 W'
assert tt.port_text('sw', 'Cam', {'port_idx': 9, 'up': False,
'sfp': {'found': True, 'kind': 'optic', 'part': 'SR', 'rx_fault': True}}) == \
'sw port 9 "Cam": down, module: optic SR, **module reports a fault**'
def test_description_skips_empty_facts_and_sections():
text = tt.description('Alert', [('Host', 'a'), ('Blank', '')], 'Summary.', ['One', 'Two'],
[('Empty', []), ('Links', ['- x'])])
assert text.startswith('## Alert\n\n- **Host:** a\n\nSummary.')
assert 'Blank' not in text and '### Empty' not in text
assert '1. One\n2. Two' in text and '### Links\n- x' in text
def test_history_lines():
assert tt.history_lines({'last_7d': 0, 'last_30d': 0}, NOW) == ['- First time in the last 30 days.']
lines = tt.history_lines({'last_7d': 1, 'last_30d': 2, 'last_first_seen': NOW - timedelta(hours=26),
'last_resolved_at': NOW - timedelta(hours=25, minutes=55)}, NOW)
assert lines[0].startswith('- Happened 1 time in the last 7 days and 2')
assert lines[1] == '- Last cleared 2026-11-09 16:05 UTC (25 h 55 min ago), after 5 min.'
def test_recovery_comment():
text = tt.recovery_comment({'event_type': 'interface_down', 'target_name': 'large1', 'target_detail': 'eno1',
'first_seen': NOW - timedelta(minutes=7), 'resolved_at': NOW,
'consecutive_failures': 3}, NOW, 'https://g/device/large1')
assert text.startswith('**✅ Cleared**: interface down on large1 eno1.')
assert '- **Lasted:** 7 min (first seen 2026-11-10 17:53 UTC)' in text
assert '- **Gandalf:** https://g/device/large1' in text
assert 'leaves this ticket open' in text