Files
FabledSteward/tests/plugins/syslog/test_syslog_page.py
T
bvandeusenandClaude Opus 5.5 51892a02c0
CI / lint (push) Failing after 2s
CI / unit (push) Failing after 1m27s
CI / integration (push) Failing after 1m35s
CI / publish (push) Skipped
feat(syslog): a syslog receiver: UDP and TCP, senders offered before they're kept, each source's lines in a ring (#5999)
- plugins/syslog: RFC 5424 and RFC 3164 parsing, RFC 6587 TCP framing.
- Lines are queued as they arrive and stored every 5 s through a Holding,
  so a storage stall delays them rather than losing them.
- A new sender is an offered source, counted and not kept, until an admin
  adds it on the Syslog page (name, host); it can be ignored instead.
- The listener follows the plugin's settings (port, UDP, TCP), rebinding
  within seconds of a save; a port it can't open is said on the page.
- Hourly ring per source: age window and byte cap from Settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:09:07 -04:00

74 lines
3.7 KiB
Python

"""The Syslog page (#5999): whether the listener is open or why not, the
sources kept, and the senders offered to an admin to add or ignore."""
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace as NS
from tests._templates import render_plugin_template
from tests.test_style_tokens import hex_colours
def _listener(**over):
base = {"port": 5514, "protocols": ["UDP", "TCP"], "listening": True, "error": None,
"bound_at": datetime.now(timezone.utc) - timedelta(hours=2), "received": 1204,
"dropped": 0, "unoffered": 0, "connections": 1, "senders_full": False, "max_senders": 200}
return {**base, **over}
def _source(name, state, level=6, line="nfsd: server not responding", host=None):
now = datetime.now(timezone.utc)
return {"id": f"id-{name}", "address": "192.168.0.7", "state": state, "name": name,
"hostname_seen": "synnas", "host": host, "first_seen_at": now - timedelta(days=1),
"last_seen_at": now - timedelta(minutes=3), "lines_total": 1204,
"severity": "err" if level == 3 else "info", "level": level, "last_line": line,
"suggested_host": "h1"}
async def _render(app, role, **ctx):
from quart import session
ctx = {"listener": _listener(), "sources": [], "offered": [], "ignored": [],
"hosts": [NS(id="h1", name="SynNAS", address="192.168.0.7")],
"now": datetime.now(timezone.utc), **ctx}
async with app.test_request_context("/plugins/syslog/"):
session["user_role"] = role
page = await render_plugin_template(app, "syslog", "syslog/index.html", **ctx)
return " ".join(page.split())
async def test_an_offered_sender_can_be_added_or_ignored_by_an_admin(app):
page = await _render(app, "admin", offered=[_source("synnas", "offered", level=3)])
assert "Sending, not added" in page and "calls itself synnas" in page
assert 'action="/plugins/syslog/sources/id-synnas/add"' in page
assert 'action="/plugins/syslog/sources/id-synnas/ignore"' in page
assert '<option value="h1" selected>SynNAS</option>' in page # the host at its address
assert 'class="tone-crit">err</span> nfsd: server not responding' in page
assert "UDP and TCP port 5514" in page and "1,204 lines received" in page
main = page[page.index("<main"):]
assert "font-weight:600" not in main and not hex_colours(main), hex_colours(main)
async def test_a_viewer_sees_sources_but_no_controls(app):
page = await _render(app, "viewer", sources=[_source("SynNAS", "accepted", host=NS(id="h1", name="SynNAS"))],
offered=[_source("router", "offered")])
assert 'href="/hosts/h1"' in page and "/plugins/syslog/sources/" not in page
assert "Sending, not added" in page
async def test_a_port_it_cannot_open_is_said(app):
page = await _render(app, "admin", listener=_listener(
listening=False, error="Port 514 couldn't be opened (UDP: Permission denied)."))
assert "Not listening" in page and "Port 514 couldn't be opened" in page
assert "below 1024" in page
assert "No sources yet." in page
async def test_the_edit_page_has_its_trail_and_a_remove(app):
from quart import session
source = NS(id="s1", name="SynNAS", hostname_seen="synnas", address="192.168.0.7", host_id=None)
async with app.test_request_context("/plugins/syslog/sources/s1/edit"):
session["user_role"] = "admin"
page = await render_plugin_template(app, "syslog", "syslog/edit.html", source=source,
hosts=[], label="SynNAS")
page = " ".join(page.split())
assert 'href="/plugins/syslog/">Syslog</a>' in page
assert 'action="/plugins/syslog/sources/s1/delete"' in page and "data-confirm=" in page