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
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>
65 lines
2.7 KiB
Python
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
|