Server-side RMM telemetry plugin: current-snapshot endpointstates plus append-only curated endpointevents, resolved to existing Computer assets by hostname. Adds ADR-010 asset panels (status, services, recent events), dashboard quiet and stability cards, and a minimal Fleet list view that links into existing PC detail pages. Registers the activity sidebar icon and a dev-only sample seeder. Ingest arrives on the generic collector route; the future C# agent design record lives in docs/DESIGN-DECISIONS-agent.md.
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""Endpoints plugin smoke + collector-contract tests.
|
|
|
|
Asserts the plugin loads cleanly, satisfies the framework contract, and that
|
|
its ADR-006 collector schema is well formed. Extend with DB-backed ingest tests
|
|
as the plugin grows.
|
|
"""
|
|
|
|
from flask import Blueprint
|
|
|
|
from plugins.endpoints.plugin import EndpointsPlugin, CURATED_EVENT_IDS
|
|
from plugins.endpoints.eventcatalog import EVENT_CATALOG, CRITICAL_EVENT_IDS
|
|
|
|
|
|
def test_endpoints_plugin_meta_is_valid():
|
|
"""meta returns a PluginMeta with the expected name and prefix."""
|
|
plugin = EndpointsPlugin()
|
|
assert plugin.meta.name == 'endpoints'
|
|
assert plugin.meta.api_prefix == '/api/endpoints'
|
|
assert 'computers' in plugin.meta.dependencies
|
|
|
|
|
|
def test_endpoints_plugin_get_blueprint_returns_blueprint():
|
|
"""get_blueprint returns a Flask Blueprint, not None."""
|
|
plugin = EndpointsPlugin()
|
|
assert isinstance(plugin.get_blueprint(), Blueprint)
|
|
|
|
|
|
def test_endpoints_plugin_get_models_returns_models():
|
|
"""get_models returns the two telemetry tables."""
|
|
plugin = EndpointsPlugin()
|
|
models = plugin.get_models()
|
|
assert len(models) >= 2
|
|
for model in models:
|
|
assert hasattr(model, '__tablename__')
|
|
|
|
|
|
def test_endpoints_collector_schema_identity_is_hostname():
|
|
"""ADR-006 schema declares hostname as the identity field."""
|
|
plugin = EndpointsPlugin()
|
|
schema = plugin.get_collector_schema()
|
|
assert schema is not None
|
|
assert schema['identityfield'] == 'hostname'
|
|
assert 'hostname' in schema['fields']['required']
|
|
|
|
|
|
def test_endpoints_curated_event_ids_include_core_security_ids():
|
|
"""The curated catalog covers the agreed logon/stability IDs."""
|
|
for eventid in (4624, 4625, 4634, 4672, 4697, 4698, 41, 4618, 55, 7011,
|
|
10016, 1001):
|
|
assert eventid in CURATED_EVENT_IDS
|
|
|
|
|
|
def test_curated_ids_match_the_event_catalog():
|
|
"""Ingest filter and display catalog cannot drift apart."""
|
|
assert CURATED_EVENT_IDS == frozenset(EVENT_CATALOG)
|
|
|
|
|
|
def test_critical_event_ids_are_a_subset_of_the_catalog():
|
|
"""Dashboard stability card only queries known critical IDs."""
|
|
assert set(CRITICAL_EVENT_IDS)
|
|
assert set(CRITICAL_EVENT_IDS).issubset(set(EVENT_CATALOG))
|
|
|
|
|
|
def test_endpoints_asset_panels_are_computer_scoped():
|
|
"""Every asset panel targets computer assets only, with an endpoint."""
|
|
plugin = EndpointsPlugin()
|
|
panels = plugin.get_asset_panels()
|
|
assert len(panels) == 3
|
|
for panel in panels:
|
|
assert panel['assettypes'] == ['computer']
|
|
assert panel['endpoint'].startswith('/api/endpoints/asset/{assetid}')
|
|
assert panel['render'] in ('keyvalue', 'list')
|
|
|
|
|
|
def test_endpoints_dashboard_widgets_declared():
|
|
"""Quiet + stability exception cards are declared and hide when empty."""
|
|
plugin = EndpointsPlugin()
|
|
widgets = plugin.get_dashboard_widgets()
|
|
ids = {w['id'] for w in widgets}
|
|
assert {'endpoints-quiet', 'endpoints-stability'} <= ids
|
|
for widget in widgets:
|
|
assert widget['render'] == 'exceptions'
|
|
assert widget['empty'] == 'hide'
|
|
assert widget['permission'] == 'endpoints.view'
|
|
|
|
|
|
def test_endpoints_navigation_item_points_at_fleet_view():
|
|
"""The fleet list view is exposed as a single /endpoints nav item."""
|
|
plugin = EndpointsPlugin()
|
|
items = plugin.get_navigation_items()
|
|
routes = {item['route'] for item in items}
|
|
assert '/endpoints' in routes
|